2 * $Id: buttons.c,v 1.106 2021/01/17 17:03:16 tom Exp $
4 * buttons.c -- draw buttons, e.g., OK/Cancel
6 * Copyright 2000-2020,2021 Thomas E. Dickey
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.
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.
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.
31 #define MIN_BUTTON (-dialog_state.visit_cols)
32 #define CHR_BUTTON (!dialog_state.plain_buttons)
35 center_label(char *buffer, int longest, const char *label)
37 int len = dlg_count_columns(label);
42 int left = (longest - len) / 2;
43 right = (longest - len - left);
45 sprintf(buffer, "%*s", left, " ");
47 strcat(buffer, label);
49 sprintf(buffer + strlen(buffer), "%*s", right, " ");
53 * Parse a multibyte character out of the string, set it past the parsed
57 string_to_char(const char **stringp)
60 #ifdef USE_WIDE_CURSES
61 const char *string = *stringp;
62 size_t have = strlen(string);
67 memset(&state, 0, sizeof(state));
68 len = mbrlen(string, have, &state);
70 if ((int) len > 0 && len <= have) {
73 memset(&state, 0, sizeof(state));
74 memset(cmp2, 0, sizeof(cmp2));
75 check = mbrtowc(cmp2, string, len, &state);
80 cmp2[0] = UCH(*string);
85 const char *string = *stringp;
86 result = UCH(*string);
93 count_labels(const char **labels)
97 while (*labels++ != 0) {
105 * Check if the latest key should be added to the hotkey list.
108 was_hotkey(int this_key, int *used_keys, size_t next)
114 for (n = 0; n < next; ++n) {
115 if (used_keys[n] == this_key) {
125 * Determine the hot-keys for a set of button-labels. Normally these are
126 * the first uppercase character in each label. However, if more than one
127 * button has the same first-uppercase, then we will (attempt to) look for
130 * This allocates data which must be freed by the caller.
133 get_hotkeys(const char **labels)
136 size_t count = count_labels(labels);
138 if ((result = dlg_calloc(int, count + 1)) != 0) {
141 for (n = 0; n < count; ++n) {
142 const char *label = labels[n];
143 const int *indx = dlg_index_wchars(label);
144 int limit = dlg_count_wchars(label);
147 for (i = 0; i < limit; ++i) {
149 int check = UCH(label[first]);
150 #ifdef USE_WIDE_CURSES
151 int last = indx[i + 1];
152 if ((last - first) != 1) {
153 const char *temp = (label + first);
154 check = string_to_char(&temp);
157 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
177 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
180 HOTKEY state = sFIND_KEY;
181 const int *indx = dlg_index_wchars(label);
182 int limit = dlg_count_wchars(label);
183 chtype key_attr = (selected
184 ? button_key_active_attr
185 : button_key_inactive_attr);
186 chtype label_attr = (selected
187 ? button_label_active_attr
188 : button_label_inactive_attr);
190 (void) wmove(win, y, x);
191 dlg_attrset(win, selected
193 : button_inactive_attr);
194 (void) waddstr(win, "<");
195 dlg_attrset(win, label_attr);
196 for (i = 0; i < limit; ++i) {
199 int last = indx[i + 1];
203 check = UCH(label[first]);
204 #ifdef USE_WIDE_CURSES
205 if ((last - first) != 1) {
206 const char *temp = (label + first);
207 check = string_to_char(&temp);
210 if (check == hotkey) {
211 dlg_attrset(win, key_attr);
216 dlg_attrset(win, label_attr);
222 waddnstr(win, label + first, last - first);
224 dlg_attrset(win, selected
226 : button_inactive_attr);
227 (void) waddstr(win, ">");
228 if (!dialog_vars.cursor_off_label) {
229 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
234 * Count the buttons in the list.
237 dlg_button_count(const char **labels)
240 while (*labels++ != 0)
246 * Compute the size of the button array in columns. Return the total number of
247 * columns in *length, and the longest button's columns in *longest
250 dlg_button_sizes(const char **labels,
259 for (n = 0; labels[n] != 0; n++) {
264 int len = dlg_count_columns(labels[n]);
271 * If we can, make all of the buttons the same size. This is only optional
272 * for buttons laid out horizontally.
274 if (*longest < 6 - (*longest & 1))
275 *longest = 6 - (*longest & 1);
277 *length = *longest * n;
281 * Compute the size of the button array.
284 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
286 int count = dlg_button_count(labels);
296 dlg_button_sizes(labels, FALSE, &longest, &length);
297 used = (length + (count * 2));
298 unused = limit - used;
300 if ((*gap = unused / (count + 3)) <= 0) {
301 if ((*gap = unused / (count + 1)) <= 0)
307 *step = *gap + (used + count - 1) / count;
308 result = (*gap > 0) && (unused >= 0);
316 * Make sure there is enough space for the buttons
319 dlg_button_layout(const char **labels, int *limit)
321 int gap, margin, step;
323 if (labels != 0 && dlg_button_count(labels)) {
326 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
328 width += (4 * MARGIN);
337 * Print a list of buttons at the given position.
340 dlg_draw_buttons(WINDOW *win,
347 chtype save = dlg_get_attrs(win);
357 dlg_mouse_setbase(getbegx(win), getbegy(win));
359 getyx(win, final_y, final_x);
361 dlg_button_sizes(labels, vertical, &longest, &length);
367 dlg_button_x_step(labels, limit, &gap, &margin, &step);
372 * Allocate a buffer big enough for any label.
374 need = (size_t) longest;
378 int *hotkeys = get_hotkeys(labels);
380 assert_ptr(hotkeys, "dlg_draw_buttons");
382 for (n = 0; labels[n] != 0; ++n) {
383 need += strlen(labels[n]) + 1;
385 buffer = dlg_malloc(char, need);
386 assert_ptr(buffer, "dlg_draw_buttons");
391 for (n = 0; labels[n] != 0; n++) {
392 center_label(buffer, longest, labels[n]);
393 mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
394 print_button(win, buffer,
395 CHR_BUTTON ? hotkeys[n] : -1,
397 (selected == n) || (n == 0 && selected < 0));
399 getyx(win, final_y, final_x);
402 if ((y += step) > limit)
405 if ((x += step) > limit)
409 (void) wmove(win, final_y, final_x);
411 dlg_attrset(win, save);
418 * Match a given character against the beginning of the string, ignoring case
419 * of the given character. The matching string must begin with an uppercase
423 dlg_match_char(int ch, const char *string)
425 if (!dialog_vars.no_hot_list) {
427 int cmp2 = string_to_char(&string);
428 #ifdef USE_WIDE_CURSES
429 wint_t cmp1 = dlg_toupper(ch);
430 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
434 if (ch > 0 && ch < 256) {
435 if (dlg_toupper(ch) == dlg_toupper(cmp2))
445 * Find the first uppercase character in the label, which we may use for an
449 dlg_button_to_char(const char *label)
453 while (*label != 0) {
454 int ch = string_to_char(&label);
455 if (dlg_isupper(ch)) {
464 * Given a list of button labels, and a character which may be the abbreviation
465 * for one, find it, if it exists. An abbreviation will be the first character
466 * which happens to be capitalized in the label.
469 dlg_char_to_button(int ch, const char **labels)
471 int result = DLG_EXIT_UNKNOWN;
474 int *hotkeys = get_hotkeys(labels);
476 ch = (int) dlg_toupper(dlg_last_getc());
481 for (j = 0; labels[j] != 0; ++j) {
482 if (ch == hotkeys[j]) {
498 return (dialog_vars.yes_label != NULL)
499 ? dialog_vars.yes_label
506 return (dialog_vars.no_label != NULL)
507 ? dialog_vars.no_label
514 return (dialog_vars.ok_label != NULL)
515 ? dialog_vars.ok_label
520 my_cancel_label(void)
522 return (dialog_vars.cancel_label != NULL)
523 ? dialog_vars.cancel_label
530 return (dialog_vars.exit_label != NULL)
531 ? dialog_vars.exit_label
538 return (dialog_vars.extra_label != NULL)
539 ? dialog_vars.extra_label
546 return (dialog_vars.help_label != NULL)
547 ? dialog_vars.help_label
552 * Return a list of button labels.
560 if (dialog_vars.extra_button) {
561 dlg_save_vars(&save);
562 dialog_vars.nocancel = TRUE;
563 result = dlg_ok_labels();
564 dlg_restore_vars(&save);
566 static const char *labels[3];
569 if (!dialog_vars.nook)
570 labels[n++] = my_exit_label();
571 if (dialog_vars.help_button)
572 labels[n++] = my_help_label();
574 labels[n++] = my_exit_label();
583 * Map the given button index for dlg_exit_label() into our exit-code.
586 dlg_exit_buttoncode(int button)
591 dlg_save_vars(&save);
592 dialog_vars.nocancel = TRUE;
594 result = dlg_ok_buttoncode(button);
596 dlg_restore_vars(&save);
602 finish_ok_label(const char **labels, int n)
605 labels[n++] = my_ok_label();
606 dialog_vars.nook = FALSE;
607 dlg_trace_msg("# ignore --nook, since at least one button is needed\n");
615 * Return a list of button labels for the OK (no Cancel) group, used in msgbox
621 static const char *labels[4];
624 if (!dialog_vars.nook)
625 labels[n++] = my_ok_label();
626 if (dialog_vars.extra_button)
627 labels[n++] = my_extra_label();
628 if (dialog_vars.help_button)
629 labels[n++] = my_help_label();
631 return finish_ok_label(labels, n);
635 * Return a list of button labels for the OK/Cancel group, used in most widgets
636 * that select an option or data.
641 static const char *labels[5];
644 if (!dialog_vars.nook)
645 labels[n++] = my_ok_label();
646 if (dialog_vars.extra_button)
647 labels[n++] = my_extra_label();
648 if (!dialog_vars.nocancel)
649 labels[n++] = my_cancel_label();
650 if (dialog_vars.help_button)
651 labels[n++] = my_help_label();
653 return finish_ok_label(labels, n);
657 * Map the given button index for dlg_ok_labels() into our exit-code
660 dlg_ok_buttoncode(int button)
662 int result = DLG_EXIT_ERROR;
663 int n = !dialog_vars.nook;
665 if (!dialog_vars.nook && (button <= 0)) {
666 result = DLG_EXIT_OK;
667 } else if (dialog_vars.extra_button && (button == n++)) {
668 result = DLG_EXIT_EXTRA;
669 } else if (!dialog_vars.nocancel && (button == n++)) {
670 result = DLG_EXIT_CANCEL;
671 } else if (dialog_vars.help_button && (button == n)) {
672 result = DLG_EXIT_HELP;
674 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n",
675 button, result, dlg_exitcode2s(result)));
680 * Given that we're using dlg_ok_labels() to list buttons, find the next index
681 * in the list of buttons. The 'extra' parameter if negative provides a way to
682 * enumerate extra active areas on the widget.
685 dlg_next_ok_buttonindex(int current, int extra)
687 int result = current + 1;
690 && dlg_ok_buttoncode(result) < 0)
696 * Similarly, find the previous button index.
699 dlg_prev_ok_buttonindex(int current, int extra)
701 int result = current - 1;
703 if (result < extra) {
704 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
712 * Find the button-index for the "OK" or "Cancel" button, according to
713 * whether --defaultno is given. If --nocancel was given, we always return
714 * the index for the first button (usually "OK" unless --nook was used).
717 dlg_defaultno_button(void)
721 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
722 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
725 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
730 * Find the button-index for a button named with --default-button. If the
731 * option was not specified, or if the selected button does not exist, return
732 * the index of the first button (usually "OK" unless --nook was used).
735 dlg_default_button(void)
739 if (dialog_vars.default_button >= 0) {
742 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
743 if (n == dialog_vars.default_button) {
749 DLG_TRACE(("# dlg_default_button() = %d\n", result));
754 * Return a list of buttons for Yes/No labels.
761 if (dialog_vars.extra_button) {
762 result = dlg_ok_labels();
764 static const char *labels[4];
767 labels[n++] = my_yes_label();
768 labels[n++] = my_no_label();
769 if (dialog_vars.help_button)
770 labels[n++] = my_help_label();
780 * Map the given button index for dlg_yes_labels() into our exit-code.
783 dlg_yes_buttoncode(int button)
785 int result = DLG_EXIT_ERROR;
787 if (dialog_vars.extra_button) {
788 result = dlg_ok_buttoncode(button);
789 } else if (button == 0) {
790 result = DLG_EXIT_OK;
791 } else if (button == 1) {
792 result = DLG_EXIT_CANCEL;
793 } else if (button == 2 && dialog_vars.help_button) {
794 result = DLG_EXIT_HELP;
801 * Return the next index in labels[];
804 dlg_next_button(const char **labels, int button)
809 if (labels[button + 1] != 0) {
818 * Return the previous index in labels[];
821 dlg_prev_button(const char **labels, int button)
823 if (button > MIN_BUTTON) {
829 while (labels[button + 1] != 0)