2 * $Id: buttons.c,v 1.99 2018/06/18 22:11:16 tom Exp $
4 * buttons.c -- draw buttons, e.g., OK/Cancel
6 * Copyright 2000-2017,2018 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);
38 int left = 0, right = 0;
42 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);
68 memset(&state, 0, sizeof(state));
69 len = mbrlen(string, have, &state);
70 if ((int) len > 0 && len <= have) {
71 memset(&state, 0, sizeof(state));
72 memset(cmp2, 0, sizeof(cmp2));
73 check = mbrtowc(cmp2, string, len, &state);
78 cmp2[0] = UCH(*string);
83 const char *string = *stringp;
84 result = UCH(*string);
91 count_labels(const char **labels)
95 while (*labels++ != 0) {
103 * Check if the latest key should be added to the hotkey list.
106 was_hotkey(int this_key, int *used_keys, size_t next)
112 for (n = 0; n < next; ++n) {
113 if (used_keys[n] == this_key) {
123 * Determine the hot-keys for a set of button-labels. Normally these are
124 * the first uppercase character in each label. However, if more than one
125 * button has the same first-uppercase, then we will (attempt to) look for
128 * This allocates data which must be freed by the caller.
131 get_hotkeys(const char **labels)
134 size_t count = count_labels(labels);
137 if ((result = dlg_calloc(int, count + 1)) != 0) {
138 for (n = 0; n < count; ++n) {
139 const char *label = labels[n];
140 const int *indx = dlg_index_wchars(label);
141 int limit = dlg_count_wchars(label);
144 for (i = 0; i < limit; ++i) {
146 int check = UCH(label[first]);
147 #ifdef USE_WIDE_CURSES
148 int last = indx[i + 1];
149 if ((last - first) != 1) {
150 const char *temp = (label + first);
151 check = string_to_char(&temp);
154 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
174 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
177 HOTKEY state = sFIND_KEY;
178 const int *indx = dlg_index_wchars(label);
179 int limit = dlg_count_wchars(label);
180 chtype key_attr = (selected
181 ? button_key_active_attr
182 : button_key_inactive_attr);
183 chtype label_attr = (selected
184 ? button_label_active_attr
185 : button_label_inactive_attr);
187 (void) wmove(win, y, x);
188 dlg_attrset(win, selected
190 : button_inactive_attr);
191 (void) waddstr(win, "<");
192 dlg_attrset(win, label_attr);
193 for (i = 0; i < limit; ++i) {
196 int last = indx[i + 1];
200 check = UCH(label[first]);
201 #ifdef USE_WIDE_CURSES
202 if ((last - first) != 1) {
203 const char *temp = (label + first);
204 check = string_to_char(&temp);
207 if (check == hotkey) {
208 dlg_attrset(win, key_attr);
213 dlg_attrset(win, label_attr);
219 waddnstr(win, label + first, last - first);
221 dlg_attrset(win, selected
223 : button_inactive_attr);
224 (void) waddstr(win, ">");
225 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
229 * Count the buttons in the list.
232 dlg_button_count(const char **labels)
235 while (*labels++ != 0)
241 * Compute the size of the button array in columns. Return the total number of
242 * columns in *length, and the longest button's columns in *longest
245 dlg_button_sizes(const char **labels,
254 for (n = 0; labels[n] != 0; n++) {
259 int len = dlg_count_columns(labels[n]);
266 * If we can, make all of the buttons the same size. This is only optional
267 * for buttons laid out horizontally.
269 if (*longest < 6 - (*longest & 1))
270 *longest = 6 - (*longest & 1);
272 *length = *longest * n;
276 * Compute the size of the button array.
279 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
281 int count = dlg_button_count(labels);
290 dlg_button_sizes(labels, FALSE, &longest, &length);
291 used = (length + (count * 2));
292 unused = limit - used;
294 if ((*gap = unused / (count + 3)) <= 0) {
295 if ((*gap = unused / (count + 1)) <= 0)
301 *step = *gap + (used + count - 1) / count;
302 result = (*gap > 0) && (unused >= 0);
310 * Make sure there is enough space for the buttons
313 dlg_button_layout(const char **labels, int *limit)
316 int gap, margin, step;
318 if (labels != 0 && dlg_button_count(labels)) {
319 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
321 width += (4 * MARGIN);
330 * Print a list of buttons at the given position.
333 dlg_draw_buttons(WINDOW *win,
340 chtype save = dlg_get_attrs(win);
352 dlg_mouse_setbase(getbegx(win), getbegy(win));
354 getyx(win, final_y, final_x);
356 dlg_button_sizes(labels, vertical, &longest, &length);
362 dlg_button_x_step(labels, limit, &gap, &margin, &step);
367 * Allocate a buffer big enough for any label.
369 need = (size_t) longest;
371 int *hotkeys = get_hotkeys(labels);
372 assert_ptr(hotkeys, "dlg_draw_buttons");
374 for (n = 0; labels[n] != 0; ++n) {
375 need += strlen(labels[n]) + 1;
377 buffer = dlg_malloc(char, need);
378 assert_ptr(buffer, "dlg_draw_buttons");
383 for (n = 0; labels[n] != 0; n++) {
384 center_label(buffer, longest, labels[n]);
385 mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
386 print_button(win, buffer,
387 CHR_BUTTON ? hotkeys[n] : -1,
389 (selected == n) || (n == 0 && selected < 0));
391 getyx(win, final_y, final_x);
394 if ((y += step) > limit)
397 if ((x += step) > limit)
401 (void) wmove(win, final_y, final_x);
403 dlg_attrset(win, save);
410 * Match a given character against the beginning of the string, ignoring case
411 * of the given character. The matching string must begin with an uppercase
415 dlg_match_char(int ch, const char *string)
418 int cmp2 = string_to_char(&string);
419 #ifdef USE_WIDE_CURSES
420 wint_t cmp1 = dlg_toupper(ch);
421 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
425 if (ch > 0 && ch < 256) {
426 if (dlg_toupper(ch) == dlg_toupper(cmp2))
435 * Find the first uppercase character in the label, which we may use for an
439 dlg_button_to_char(const char *label)
443 while (*label != 0) {
444 cmp = string_to_char(&label);
445 if (dlg_isupper(cmp)) {
453 * Given a list of button labels, and a character which may be the abbreviation
454 * for one, find it, if it exists. An abbreviation will be the first character
455 * which happens to be capitalized in the label.
458 dlg_char_to_button(int ch, const char **labels)
460 int result = DLG_EXIT_UNKNOWN;
463 int *hotkeys = get_hotkeys(labels);
466 ch = (int) dlg_toupper(dlg_last_getc());
469 for (j = 0; labels[j] != 0; ++j) {
470 if (ch == hotkeys[j]) {
486 return (dialog_vars.yes_label != NULL)
487 ? dialog_vars.yes_label
494 return (dialog_vars.no_label != NULL)
495 ? dialog_vars.no_label
502 return (dialog_vars.ok_label != NULL)
503 ? dialog_vars.ok_label
508 my_cancel_label(void)
510 return (dialog_vars.cancel_label != NULL)
511 ? dialog_vars.cancel_label
518 return (dialog_vars.exit_label != NULL)
519 ? dialog_vars.exit_label
526 return (dialog_vars.extra_label != NULL)
527 ? dialog_vars.extra_label
534 return (dialog_vars.help_label != NULL)
535 ? dialog_vars.help_label
540 * Return a list of button labels.
548 if (dialog_vars.extra_button) {
549 dlg_save_vars(&save);
550 dialog_vars.nocancel = TRUE;
551 result = dlg_ok_labels();
552 dlg_restore_vars(&save);
554 static const char *labels[3];
557 if (!dialog_vars.nook)
558 labels[n++] = my_exit_label();
559 if (dialog_vars.help_button)
560 labels[n++] = my_help_label();
562 labels[n++] = my_exit_label();
571 * Map the given button index for dlg_exit_label() into our exit-code.
574 dlg_exit_buttoncode(int button)
579 dlg_save_vars(&save);
580 dialog_vars.nocancel = TRUE;
582 result = dlg_ok_buttoncode(button);
584 dlg_restore_vars(&save);
592 static const char *labels[4];
595 labels[n++] = my_ok_label();
596 if (dialog_vars.extra_button)
597 labels[n++] = my_extra_label();
598 if (dialog_vars.help_button)
599 labels[n++] = my_help_label();
605 * Return a list of button labels for the OK/Cancel group.
610 static const char *labels[5];
613 if (!dialog_vars.nook)
614 labels[n++] = my_ok_label();
615 if (dialog_vars.extra_button)
616 labels[n++] = my_extra_label();
617 if (!dialog_vars.nocancel)
618 labels[n++] = my_cancel_label();
619 if (dialog_vars.help_button)
620 labels[n++] = my_help_label();
626 * Map the given button index for dlg_ok_labels() into our exit-code
629 dlg_ok_buttoncode(int button)
631 int result = DLG_EXIT_ERROR;
632 int n = !dialog_vars.nook;
634 if (!dialog_vars.nook && (button <= 0)) {
635 result = DLG_EXIT_OK;
636 } else if (dialog_vars.extra_button && (button == n++)) {
637 result = DLG_EXIT_EXTRA;
638 } else if (!dialog_vars.nocancel && (button == n++)) {
639 result = DLG_EXIT_CANCEL;
640 } else if (dialog_vars.help_button && (button == n)) {
641 result = DLG_EXIT_HELP;
643 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d\n", button, result));
648 * Given that we're using dlg_ok_labels() to list buttons, find the next index
649 * in the list of buttons. The 'extra' parameter if negative provides a way to
650 * enumerate extra active areas on the widget.
653 dlg_next_ok_buttonindex(int current, int extra)
655 int result = current + 1;
658 && dlg_ok_buttoncode(result) < 0)
664 * Similarly, find the previous button index.
667 dlg_prev_ok_buttonindex(int current, int extra)
669 int result = current - 1;
671 if (result < extra) {
672 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
680 * Find the button-index for the "OK" or "Cancel" button, according to
681 * whether --defaultno is given. If --nocancel was given, we always return
682 * the index for the first button (usually "OK" unless --nook was used).
685 dlg_defaultno_button(void)
689 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
690 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
693 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
698 * Find the button-index for a button named with --default-button. If the
699 * option was not specified, or if the selected button does not exist, return
700 * the index of the first button (usually "OK" unless --nook was used).
703 dlg_default_button(void)
708 if (dialog_vars.default_button >= 0) {
709 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
710 if (n == dialog_vars.default_button) {
716 DLG_TRACE(("# dlg_default_button() = %d\n", result));
721 * Return a list of buttons for Yes/No labels.
728 if (dialog_vars.extra_button) {
729 result = dlg_ok_labels();
731 static const char *labels[4];
734 labels[n++] = my_yes_label();
735 labels[n++] = my_no_label();
736 if (dialog_vars.help_button)
737 labels[n++] = my_help_label();
747 * Map the given button index for dlg_yes_labels() into our exit-code.
750 dlg_yes_buttoncode(int button)
752 int result = DLG_EXIT_ERROR;
754 if (dialog_vars.extra_button) {
755 result = dlg_ok_buttoncode(button);
756 } else if (button == 0) {
757 result = DLG_EXIT_OK;
758 } else if (button == 1) {
759 result = DLG_EXIT_CANCEL;
760 } else if (button == 2 && dialog_vars.help_button) {
761 result = DLG_EXIT_HELP;
768 * Return the next index in labels[];
771 dlg_next_button(const char **labels, int button)
776 if (labels[button + 1] != 0) {
785 * Return the previous index in labels[];
788 dlg_prev_button(const char **labels, int button)
790 if (button > MIN_BUTTON) {
796 while (labels[button + 1] != 0)