2 * $Id: buttons.c,v 1.109 2022/04/05 23:45:54 tom Exp $
4 * buttons.c -- draw buttons, e.g., OK/Cancel
6 * Copyright 2000-2021,2022 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.
24 #include <dlg_internals.h>
27 #define MIN_BUTTON (-dialog_state.visit_cols)
28 #define CHR_BUTTON (!dialog_state.plain_buttons)
31 center_label(char *buffer, int longest, const char *label)
33 int len = dlg_count_columns(label);
38 int left = (longest - len) / 2;
39 right = (longest - len - left);
41 sprintf(buffer, "%*s", left, " ");
43 strcat(buffer, label);
45 sprintf(buffer + strlen(buffer), "%*s", right, " ");
49 * Parse a multibyte character out of the string, set it past the parsed
53 string_to_char(const char **stringp)
56 #ifdef USE_WIDE_CURSES
57 const char *string = *stringp;
58 size_t have = strlen(string);
63 memset(&state, 0, sizeof(state));
64 len = mbrlen(string, have, &state);
66 if ((int) len > 0 && len <= have) {
69 memset(&state, 0, sizeof(state));
70 memset(cmp2, 0, sizeof(cmp2));
71 check = mbrtowc(cmp2, string, len, &state);
76 cmp2[0] = UCH(*string);
81 const char *string = *stringp;
82 result = UCH(*string);
89 count_labels(const char **labels)
93 while (*labels++ != 0) {
101 * Check if the latest key should be added to the hotkey list.
104 was_hotkey(int this_key, int *used_keys, size_t next)
110 for (n = 0; n < next; ++n) {
111 if (used_keys[n] == this_key) {
121 * Determine the hot-keys for a set of button-labels. Normally these are
122 * the first uppercase character in each label. However, if more than one
123 * button has the same first-uppercase, then we will (attempt to) look for
126 * This allocates data which must be freed by the caller.
129 get_hotkeys(const char **labels)
132 size_t count = count_labels(labels);
134 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);
143 for (i = 0; i < limit; ++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);
153 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
173 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
176 HOTKEY state = sFIND_KEY;
177 const int *indx = dlg_index_wchars(label);
178 int limit = dlg_count_wchars(label);
179 chtype key_attr = (selected
180 ? button_key_active_attr
181 : button_key_inactive_attr);
182 chtype label_attr = (selected
183 ? button_label_active_attr
184 : button_label_inactive_attr);
186 (void) wmove(win, y, x);
187 dlg_attrset(win, selected
189 : button_inactive_attr);
190 (void) waddstr(win, "<");
191 dlg_attrset(win, label_attr);
192 for (i = 0; i < limit; ++i) {
195 int last = indx[i + 1];
199 check = UCH(label[first]);
200 #ifdef USE_WIDE_CURSES
201 if ((last - first) != 1) {
202 const char *temp = (label + first);
203 check = string_to_char(&temp);
206 if (check == hotkey) {
207 dlg_attrset(win, key_attr);
212 dlg_attrset(win, label_attr);
218 waddnstr(win, label + first, last - first);
220 dlg_attrset(win, selected
222 : button_inactive_attr);
223 (void) waddstr(win, ">");
224 if (!dialog_vars.cursor_off_label) {
225 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
230 * Count the buttons in the list.
233 dlg_button_count(const char **labels)
236 while (*labels++ != 0)
242 * Compute the size of the button array in columns. Return the total number of
243 * columns in *length, and the longest button's columns in *longest
246 dlg_button_sizes(const char **labels,
255 for (n = 0; labels[n] != 0; n++) {
260 int len = dlg_count_columns(labels[n]);
267 * If we can, make all of the buttons the same size. This is only optional
268 * for buttons laid out horizontally.
270 if (*longest < 6 - (*longest & 1))
271 *longest = 6 - (*longest & 1);
273 *length = *longest * n;
277 * Compute the size of the button array.
280 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
282 int count = dlg_button_count(labels);
292 dlg_button_sizes(labels, FALSE, &longest, &length);
293 used = (length + (count * 2));
294 unused = limit - used;
296 if ((*gap = unused / (count + 3)) <= 0) {
297 if ((*gap = unused / (count + 1)) <= 0)
303 *step = *gap + (used + count - 1) / count;
304 result = (*gap > 0) && (unused >= 0);
312 * Make sure there is enough space for the buttons
315 dlg_button_layout(const char **labels, int *limit)
317 int gap, margin, step;
319 if (labels != 0 && dlg_button_count(labels)) {
322 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
324 width += (4 * MARGIN);
333 * Print a list of buttons at the given position.
336 dlg_draw_buttons(WINDOW *win,
343 chtype save = dlg_get_attrs(win);
353 dlg_mouse_setbase(getbegx(win), getbegy(win));
355 getyx(win, final_y, final_x);
357 dlg_button_sizes(labels, vertical, &longest, &length);
363 dlg_button_x_step(labels, limit, &gap, &margin, &step);
368 * Allocate a buffer big enough for any label.
370 need = (size_t) longest;
374 int *hotkeys = get_hotkeys(labels);
376 assert_ptr(hotkeys, "dlg_draw_buttons");
378 for (n = 0; labels[n] != 0; ++n) {
379 need += strlen(labels[n]) + 1;
381 buffer = dlg_malloc(char, need);
382 assert_ptr(buffer, "dlg_draw_buttons");
387 for (n = 0; labels[n] != 0; n++) {
388 center_label(buffer, longest, labels[n]);
389 mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
390 print_button(win, buffer,
391 CHR_BUTTON ? hotkeys[n] : -1,
393 (selected == n) || (n == 0 && selected < 0));
395 getyx(win, final_y, final_x);
398 if ((y += step) > limit)
401 if ((x += step) > limit)
405 (void) wmove(win, final_y, final_x);
407 dlg_attrset(win, save);
414 * Match a given character against the beginning of the string, ignoring case
415 * of the given character. The matching string must begin with an uppercase
419 dlg_match_char(int ch, const char *string)
421 if (!dialog_vars.no_hot_list) {
423 int cmp2 = string_to_char(&string);
424 #ifdef USE_WIDE_CURSES
425 wint_t cmp1 = dlg_toupper(ch);
426 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
430 if (ch > 0 && ch < 256) {
431 if (dlg_toupper(ch) == dlg_toupper(cmp2))
441 * Find the first uppercase character in the label, which we may use for an
445 dlg_button_to_char(const char *label)
449 while (*label != 0) {
450 int ch = string_to_char(&label);
451 if (dlg_isupper(ch)) {
460 * Given a list of button labels, and a character which may be the abbreviation
461 * for one, find it, if it exists. An abbreviation will be the first character
462 * which happens to be capitalized in the label.
465 dlg_char_to_button(int ch, const char **labels)
467 int result = DLG_EXIT_UNKNOWN;
470 int *hotkeys = get_hotkeys(labels);
472 ch = (int) dlg_toupper(dlg_last_getc());
477 for (j = 0; labels[j] != 0; ++j) {
478 if (ch == hotkeys[j]) {
494 return (dialog_vars.yes_label != NULL)
495 ? dialog_vars.yes_label
502 return (dialog_vars.no_label != NULL)
503 ? dialog_vars.no_label
510 return (dialog_vars.ok_label != NULL)
511 ? dialog_vars.ok_label
516 my_cancel_label(void)
518 return (dialog_vars.cancel_label != NULL)
519 ? dialog_vars.cancel_label
526 return (dialog_vars.exit_label != NULL)
527 ? dialog_vars.exit_label
534 return (dialog_vars.extra_label != NULL)
535 ? dialog_vars.extra_label
542 return (dialog_vars.help_label != NULL)
543 ? dialog_vars.help_label
548 * Return a list of button labels.
556 if (dialog_vars.extra_button) {
557 dlg_save_vars(&save);
558 dialog_vars.nocancel = TRUE;
559 result = dlg_ok_labels();
560 dlg_restore_vars(&save);
562 static const char *labels[3];
565 if (!dialog_vars.nook)
566 labels[n++] = my_exit_label();
567 if (dialog_vars.help_button)
568 labels[n++] = my_help_label();
570 labels[n++] = my_exit_label();
579 * Map the given button index for dlg_exit_label() into our exit-code.
582 dlg_exit_buttoncode(int button)
587 dlg_save_vars(&save);
588 dialog_vars.nocancel = TRUE;
590 result = dlg_ok_buttoncode(button);
592 dlg_restore_vars(&save);
598 finish_ok_label(const char **labels, int n)
601 labels[n++] = my_ok_label();
602 dialog_vars.nook = FALSE;
603 DLG_TRACE(("# ignore --nook, since at least one button is needed\n"));
611 * Return a list of button labels for the OK (no Cancel) group, used in msgbox
617 static const char *labels[4];
620 if (!dialog_vars.nook)
621 labels[n++] = my_ok_label();
622 if (dialog_vars.extra_button)
623 labels[n++] = my_extra_label();
624 if (dialog_vars.help_button)
625 labels[n++] = my_help_label();
627 return finish_ok_label(labels, n);
631 * Return a list of button labels for the OK/Cancel group, used in most widgets
632 * that select an option or data.
637 static const char *labels[5];
640 if (!dialog_vars.nook)
641 labels[n++] = my_ok_label();
642 if (dialog_vars.extra_button)
643 labels[n++] = my_extra_label();
644 if (!dialog_vars.nocancel)
645 labels[n++] = my_cancel_label();
646 if (dialog_vars.help_button)
647 labels[n++] = my_help_label();
649 return finish_ok_label(labels, n);
653 * Map the given button index for dlg_ok_labels() into our exit-code
656 dlg_ok_buttoncode(int button)
658 int result = DLG_EXIT_ERROR;
659 int n = !dialog_vars.nook;
661 if (!dialog_vars.nook && (button <= 0)) {
662 result = DLG_EXIT_OK;
663 } else if (dialog_vars.extra_button && (button == n++)) {
664 result = DLG_EXIT_EXTRA;
665 } else if (!dialog_vars.nocancel && (button == n++)) {
666 result = DLG_EXIT_CANCEL;
667 } else if (dialog_vars.help_button && (button == n)) {
668 result = DLG_EXIT_HELP;
670 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n",
671 button, result, dlg_exitcode2s(result)));
676 * Given that we're using dlg_ok_labels() to list buttons, find the next index
677 * in the list of buttons. The 'extra' parameter if negative provides a way to
678 * enumerate extra active areas on the widget.
681 dlg_next_ok_buttonindex(int current, int extra)
683 int result = current + 1;
686 && dlg_ok_buttoncode(result) < 0)
692 * Similarly, find the previous button index.
695 dlg_prev_ok_buttonindex(int current, int extra)
697 int result = current - 1;
699 if (result < extra) {
700 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
708 * Find the button-index for the "OK" or "Cancel" button, according to
709 * whether --defaultno is given. If --nocancel was given, we always return
710 * the index for the first button (usually "OK" unless --nook was used).
713 dlg_defaultno_button(void)
717 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
718 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
721 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
726 * Find the button-index for a button named with --default-button. If the
727 * option was not specified, or if the selected button does not exist, return
728 * the index of the first button (usually "OK" unless --nook was used).
731 dlg_default_button(void)
735 if (dialog_vars.default_button >= 0) {
738 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
739 if (n == dialog_vars.default_button) {
745 DLG_TRACE(("# dlg_default_button() = %d\n", result));
750 * Return a list of buttons for Yes/No labels.
755 static const char *labels[5];
759 labels[n++] = my_yes_label();
760 if (dialog_vars.extra_button)
761 labels[n++] = my_extra_label();
762 labels[n++] = my_no_label();
763 if (dialog_vars.help_button)
764 labels[n++] = my_help_label();
773 * Map the given button index for dlg_yes_labels() into our exit-code.
776 dlg_yes_buttoncode(int button)
778 int result = DLG_EXIT_ERROR;
780 if (dialog_vars.extra_button) {
781 result = dlg_ok_buttoncode(button);
782 } else if (button == 0) {
783 result = DLG_EXIT_OK;
784 } else if (button == 1) {
785 result = DLG_EXIT_CANCEL;
786 } else if (button == 2 && dialog_vars.help_button) {
787 result = DLG_EXIT_HELP;
794 * Return the next index in labels[];
797 dlg_next_button(const char **labels, int button)
802 if (labels[button + 1] != 0) {
811 * Return the previous index in labels[];
814 dlg_prev_button(const char **labels, int button)
816 if (button > MIN_BUTTON) {
822 while (labels[button + 1] != 0)