buttons.c revision 244850
199158Sdes/* 2115619Sdes * $Id: buttons.c,v 1.86 2011/06/28 10:46:46 tom Exp $ 399158Sdes * 499158Sdes * buttons.c -- draw buttons, e.g., OK/Cancel 599158Sdes * 699158Sdes * Copyright 2000-2010,2011 Thomas E. Dickey 799158Sdes * 899158Sdes * This program is free software; you can redistribute it and/or modify 999158Sdes * it under the terms of the GNU Lesser General Public License, version 2.1 1099158Sdes * as published by the Free Software Foundation. 1199158Sdes * 1299158Sdes * This program is distributed in the hope that it will be useful, but 1399158Sdes * WITHOUT ANY WARRANTY; without even the implied warranty of 1499158Sdes * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 1599158Sdes * Lesser General Public License for more details. 1699158Sdes * 1799158Sdes * You should have received a copy of the GNU Lesser General Public 1899158Sdes * License along with this program; if not, write to 1999158Sdes * Free Software Foundation, Inc. 2099158Sdes * 51 Franklin St., Fifth Floor 2199158Sdes * Boston, MA 02110, USA. 2299158Sdes */ 2399158Sdes 2499158Sdes#include <dialog.h> 2599158Sdes#include <dlg_keys.h> 2699158Sdes 2799158Sdes#ifdef NEED_WCHAR_H 2899158Sdes#include <wchar.h> 2999158Sdes#endif 3099158Sdes 3199158Sdes#define MIN_BUTTON (dialog_state.visit_items ? -1 : 0) 3299158Sdes 3399158Sdesstatic void 34115619Sdescenter_label(char *buffer, int longest, const char *label) 3599158Sdes{ 3699158Sdes int len = dlg_count_columns(label); 3799158Sdes int left = 0, right = 0; 3899158Sdes 3999158Sdes *buffer = 0; 4099158Sdes if (len < longest) { 4199158Sdes left = (longest - len) / 2; 4299158Sdes right = (longest - len - left); 4399158Sdes if (left > 0) 4499158Sdes sprintf(buffer, "%*s", left, " "); 45115619Sdes } 46115619Sdes strcat(buffer, label); 47115619Sdes if (right > 0) 48115619Sdes sprintf(buffer + strlen(buffer), "%*s", right, " "); 4999158Sdes} 50107937Sdes 5199158Sdes/* 5299158Sdes * Parse a multibyte character out of the string, set it past the parsed 5399158Sdes * character. 5499158Sdes */ 5599158Sdesstatic int 5699158Sdesstring_to_char(const char **stringp) 5799158Sdes{ 5899158Sdes int result; 5999158Sdes#ifdef USE_WIDE_CURSES 6099158Sdes const char *string = *stringp; 6199158Sdes size_t have = strlen(string); 6299158Sdes size_t check; 6399158Sdes size_t len; 6499158Sdes wchar_t cmp2[2]; 6599158Sdes mbstate_t state; 6699158Sdes 6799158Sdes memset(&state, 0, sizeof(state)); 6899158Sdes len = mbrlen(string, have, &state); 6999158Sdes if ((int) len > 0 && len <= have) { 7099158Sdes memset(&state, 0, sizeof(state)); 7199158Sdes memset(cmp2, 0, sizeof(cmp2)); 7299158Sdes check = mbrtowc(cmp2, string, len, &state); 7399158Sdes if ((int) check <= 0) 7499158Sdes cmp2[0] = 0; 7599158Sdes *stringp += len; 7699158Sdes } else { 7799158Sdes cmp2[0] = UCH(*string); 7899158Sdes *stringp += 1; 7999158Sdes } 8099158Sdes result = cmp2[0]; 8199158Sdes#else 8299158Sdes const char *string = *stringp; 8399158Sdes result = UCH(*string); 8499158Sdes *stringp += 1; 8599158Sdes#endif 8699158Sdes return result; 8799158Sdes} 8899158Sdes 8999158Sdes/* 9099158Sdes * Print a button 9199158Sdes */ 9299158Sdesstatic void 9399158Sdesprint_button(WINDOW *win, char *label, int y, int x, int selected) 9499158Sdes{ 9599158Sdes int i; 9699158Sdes int state = 0; 9799158Sdes const int *indx = dlg_index_wchars(label); 9899158Sdes int limit = dlg_count_wchars(label); 9999158Sdes chtype key_attr = (selected 10099158Sdes ? button_key_active_attr 10199158Sdes : button_key_inactive_attr); 10299158Sdes chtype label_attr = (selected 10399158Sdes ? button_label_active_attr 10499158Sdes : button_label_inactive_attr); 10599158Sdes 10699158Sdes (void) wmove(win, y, x); 10799158Sdes wattrset(win, selected 10899158Sdes ? button_active_attr 10999158Sdes : button_inactive_attr); 11099158Sdes (void) waddstr(win, "<"); 11199158Sdes wattrset(win, label_attr); 11299158Sdes for (i = 0; i < limit; ++i) { 11399158Sdes int first = indx[i]; 11499158Sdes int last = indx[i + 1]; 11599158Sdes 11699158Sdes switch (state) { 11799158Sdes case 0: 11899158Sdes#ifdef USE_WIDE_CURSES 11999158Sdes if ((last - first) != 1) { 12099158Sdes const char *temp = (label + first); 12199158Sdes int cmp = string_to_char(&temp); 12299158Sdes if (dlg_isupper(cmp)) { 12399158Sdes wattrset(win, key_attr); 12499158Sdes state = 1; 12599158Sdes } 12699158Sdes break; 12799158Sdes } 12899158Sdes#endif 12999158Sdes if (dlg_isupper(UCH(label[first]))) { 13099158Sdes wattrset(win, key_attr); 13199158Sdes state = 1; 13299158Sdes } 13399158Sdes break; 13499158Sdes case 1: 13599158Sdes wattrset(win, label_attr); 13699158Sdes state = 2; 13799158Sdes break; 13899158Sdes } 13999158Sdes waddnstr(win, label + first, last - first); 14099158Sdes } 14199158Sdes wattrset(win, selected 14299158Sdes ? button_active_attr 14399158Sdes : button_inactive_attr); 14499158Sdes (void) waddstr(win, ">"); 14599158Sdes (void) wmove(win, y, x + ((int) strspn(label, " ")) + 1); 14699158Sdes} 14799158Sdes 14899158Sdes/* 14999158Sdes * Count the buttons in the list. 15099158Sdes */ 15199158Sdesint 15299158Sdesdlg_button_count(const char **labels) 15399158Sdes{ 15499158Sdes int result = 0; 15599158Sdes while (*labels++ != 0) 15699158Sdes ++result; 15799158Sdes return result; 15899158Sdes} 15999158Sdes 16099158Sdes/* 16199158Sdes * Compute the size of the button array in columns. Return the total number of 16299158Sdes * columns in *length, and the longest button's columns in *longest 16399158Sdes */ 16499158Sdesvoid 16599158Sdesdlg_button_sizes(const char **labels, 16699158Sdes int vertical, 16799158Sdes int *longest, 16899158Sdes 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 */ 198int 199dlg_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 */ 227void 228dlg_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 */ 247void 248dlg_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 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 */ 321int 322dlg_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 */ 345int 346dlg_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 */ 364int 365dlg_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 382static const char * 383my_yes_label(void) 384{ 385 return (dialog_vars.yes_label != NULL) 386 ? dialog_vars.yes_label 387 : _("Yes"); 388} 389 390static const char * 391my_no_label(void) 392{ 393 return (dialog_vars.no_label != NULL) 394 ? dialog_vars.no_label 395 : _("No"); 396} 397 398static const char * 399my_ok_label(void) 400{ 401 return (dialog_vars.ok_label != NULL) 402 ? dialog_vars.ok_label 403 : _("OK"); 404} 405 406static const char * 407my_cancel_label(void) 408{ 409 return (dialog_vars.cancel_label != NULL) 410 ? dialog_vars.cancel_label 411 : _("Cancel"); 412} 413 414static const char * 415my_exit_label(void) 416{ 417 return (dialog_vars.exit_label != NULL) 418 ? dialog_vars.exit_label 419 : _("EXIT"); 420} 421 422static const char * 423my_extra_label(void) 424{ 425 return (dialog_vars.extra_label != NULL) 426 ? dialog_vars.extra_label 427 : _("Extra"); 428} 429 430static const char * 431my_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 */ 441const char ** 442dlg_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 */ 472int 473dlg_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 488const char ** 489dlg_ok_label(void) 490{ 491 static const char *labels[3]; 492 int n = 0; 493 494 labels[n++] = my_ok_label(); 495 if (dialog_vars.help_button) 496 labels[n++] = my_help_label(); 497 labels[n] = 0; 498 return labels; 499} 500 501/* 502 * Return a list of button labels for the OK/Cancel group. 503 */ 504const char ** 505dlg_ok_labels(void) 506{ 507 static const char *labels[5]; 508 int n = 0; 509 510 if (!dialog_vars.nook) 511 labels[n++] = my_ok_label(); 512 if (dialog_vars.extra_button) 513 labels[n++] = my_extra_label(); 514 if (!dialog_vars.nocancel) 515 labels[n++] = my_cancel_label(); 516 if (dialog_vars.help_button) 517 labels[n++] = my_help_label(); 518 labels[n] = 0; 519 return labels; 520} 521 522/* 523 * Map the given button index for dlg_ok_labels() into our exit-code 524 */ 525int 526dlg_ok_buttoncode(int button) 527{ 528 int result = DLG_EXIT_ERROR; 529 int n = !dialog_vars.nook; 530 531 if (!dialog_vars.nook && (button <= 0)) { 532 result = DLG_EXIT_OK; 533 } else if (dialog_vars.extra_button && (button == n++)) { 534 result = DLG_EXIT_EXTRA; 535 } else if (!dialog_vars.nocancel && (button == n++)) { 536 result = DLG_EXIT_CANCEL; 537 } else if (dialog_vars.help_button && (button == n)) { 538 result = DLG_EXIT_HELP; 539 } 540 return result; 541} 542 543/* 544 * Given that we're using dlg_ok_labels() to list buttons, find the next index 545 * in the list of buttons. The 'extra' parameter if negative provides a way to 546 * enumerate extra active areas on the widget. 547 */ 548int 549dlg_next_ok_buttonindex(int current, int extra) 550{ 551 int result = current + 1; 552 553 if (current >= 0 554 && dlg_ok_buttoncode(result) < 0) 555 result = extra; 556 return result; 557} 558 559/* 560 * Similarly, find the previous button index. 561 */ 562int 563dlg_prev_ok_buttonindex(int current, int extra) 564{ 565 int result = current - 1; 566 567 if (result < extra) { 568 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) { 569 ; 570 } 571 } 572 return result; 573} 574 575/* 576 * Find the button-index for the "OK" or "Cancel" button, according to 577 * whether --defaultno is given. If --nocancel was given, we always return 578 * the index for "OK". 579 */ 580int 581dlg_defaultno_button(void) 582{ 583 int result = 0; 584 585 if (dialog_vars.defaultno && !dialog_vars.nocancel) { 586 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL) 587 ++result; 588 } 589 return result; 590} 591 592/* 593 * Return a list of buttons for Yes/No labels. 594 */ 595const char ** 596dlg_yes_labels(void) 597{ 598 const char **result; 599 600 if (dialog_vars.extra_button) { 601 result = dlg_ok_labels(); 602 } else { 603 static const char *labels[4]; 604 int n = 0; 605 606 labels[n++] = my_yes_label(); 607 labels[n++] = my_no_label(); 608 if (dialog_vars.help_button) 609 labels[n++] = my_help_label(); 610 labels[n] = 0; 611 612 result = labels; 613 } 614 615 return result; 616} 617 618/* 619 * Map the given button index for dlg_yes_labels() into our exit-code. 620 */ 621int 622dlg_yes_buttoncode(int button) 623{ 624 int result = DLG_EXIT_ERROR; 625 626 if (dialog_vars.extra_button) { 627 result = dlg_ok_buttoncode(button); 628 } else if (button == 0) { 629 result = DLG_EXIT_OK; 630 } else if (button == 1) { 631 result = DLG_EXIT_CANCEL; 632 } else if (button == 2 && dialog_vars.help_button) { 633 result = DLG_EXIT_HELP; 634 } 635 636 return result; 637} 638 639/* 640 * Return the next index in labels[]; 641 */ 642int 643dlg_next_button(const char **labels, int button) 644{ 645 if (labels[button + 1] != 0) 646 ++button; 647 else 648 button = MIN_BUTTON; 649 return button; 650} 651 652/* 653 * Return the previous index in labels[]; 654 */ 655int 656dlg_prev_button(const char **labels, int button) 657{ 658 if (button > MIN_BUTTON) 659 --button; 660 else { 661 while (labels[button + 1] != 0) 662 ++button; 663 } 664 return button; 665} 666