2 * $Id: buttons.c,v 1.96 2015/01/25 23:52:54 tom Exp $
4 * buttons.c -- draw buttons, e.g., OK/Cancel
6 * Copyright 2000-2014,2015 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)) {
168 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
172 const int *indx = dlg_index_wchars(label);
173 int limit = dlg_count_wchars(label);
174 chtype key_attr = (selected
175 ? button_key_active_attr
176 : button_key_inactive_attr);
177 chtype label_attr = (selected
178 ? button_label_active_attr
179 : button_label_inactive_attr);
181 (void) wmove(win, y, x);
182 (void) wattrset(win, selected
184 : button_inactive_attr);
185 (void) waddstr(win, "<");
186 (void) wattrset(win, label_attr);
187 for (i = 0; i < limit; ++i) {
190 int last = indx[i + 1];
194 check = UCH(label[first]);
195 #ifdef USE_WIDE_CURSES
196 if ((last - first) != 1) {
197 const char *temp = (label + first);
198 check = string_to_char(&temp);
201 if (check == hotkey) {
202 (void) wattrset(win, key_attr);
207 wattrset(win, label_attr);
211 waddnstr(win, label + first, last - first);
213 (void) wattrset(win, selected
215 : button_inactive_attr);
216 (void) waddstr(win, ">");
217 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
221 * Count the buttons in the list.
224 dlg_button_count(const char **labels)
227 while (*labels++ != 0)
233 * Compute the size of the button array in columns. Return the total number of
234 * columns in *length, and the longest button's columns in *longest
237 dlg_button_sizes(const char **labels,
246 for (n = 0; labels[n] != 0; n++) {
251 int len = dlg_count_columns(labels[n]);
258 * If we can, make all of the buttons the same size. This is only optional
259 * for buttons laid out horizontally.
261 if (*longest < 6 - (*longest & 1))
262 *longest = 6 - (*longest & 1);
264 *length = *longest * n;
268 * Compute the size of the button array.
271 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
273 int count = dlg_button_count(labels);
282 dlg_button_sizes(labels, FALSE, &longest, &length);
283 used = (length + (count * 2));
284 unused = limit - used;
286 if ((*gap = unused / (count + 3)) <= 0) {
287 if ((*gap = unused / (count + 1)) <= 0)
293 *step = *gap + (used + count - 1) / count;
294 result = (*gap > 0) && (unused >= 0);
302 * Make sure there is enough space for the buttons
305 dlg_button_layout(const char **labels, int *limit)
308 int gap, margin, step;
310 if (labels != 0 && dlg_button_count(labels)) {
311 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
313 width += (4 * MARGIN);
322 * Print a list of buttons at the given position.
325 dlg_draw_buttons(WINDOW *win,
332 chtype save = dlg_get_attrs(win);
344 dlg_mouse_setbase(getbegx(win), getbegy(win));
346 getyx(win, final_y, final_x);
348 dlg_button_sizes(labels, vertical, &longest, &length);
354 dlg_button_x_step(labels, limit, &gap, &margin, &step);
359 * Allocate a buffer big enough for any label.
361 need = (size_t) longest;
363 int *hotkeys = get_hotkeys(labels);
364 assert_ptr(hotkeys, "dlg_draw_buttons");
366 for (n = 0; labels[n] != 0; ++n) {
367 need += strlen(labels[n]) + 1;
369 buffer = dlg_malloc(char, need);
370 assert_ptr(buffer, "dlg_draw_buttons");
375 for (n = 0; labels[n] != 0; n++) {
376 center_label(buffer, longest, labels[n]);
377 mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
378 print_button(win, buffer,
379 CHR_BUTTON ? hotkeys[n] : -1,
381 (selected == n) || (n == 0 && selected < 0));
383 getyx(win, final_y, final_x);
386 if ((y += step) > limit)
389 if ((x += step) > limit)
393 (void) wmove(win, final_y, final_x);
395 (void) wattrset(win, save);
402 * Match a given character against the beginning of the string, ignoring case
403 * of the given character. The matching string must begin with an uppercase
407 dlg_match_char(int ch, const char *string)
410 int cmp2 = string_to_char(&string);
411 #ifdef USE_WIDE_CURSES
412 wint_t cmp1 = dlg_toupper(ch);
413 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
417 if (ch > 0 && ch < 256) {
418 if (dlg_toupper(ch) == dlg_toupper(cmp2))
427 * Find the first uppercase character in the label, which we may use for an
431 dlg_button_to_char(const char *label)
435 while (*label != 0) {
436 cmp = string_to_char(&label);
437 if (dlg_isupper(cmp)) {
445 * Given a list of button labels, and a character which may be the abbreviation
446 * for one, find it, if it exists. An abbreviation will be the first character
447 * which happens to be capitalized in the label.
450 dlg_char_to_button(int ch, const char **labels)
452 int result = DLG_EXIT_UNKNOWN;
455 int *hotkeys = get_hotkeys(labels);
458 ch = (int) dlg_toupper(dlg_last_getc());
461 for (j = 0; labels[j] != 0; ++j) {
462 if (ch == hotkeys[j]) {
478 return (dialog_vars.yes_label != NULL)
479 ? dialog_vars.yes_label
486 return (dialog_vars.no_label != NULL)
487 ? dialog_vars.no_label
494 return (dialog_vars.ok_label != NULL)
495 ? dialog_vars.ok_label
500 my_cancel_label(void)
502 return (dialog_vars.cancel_label != NULL)
503 ? dialog_vars.cancel_label
510 return (dialog_vars.exit_label != NULL)
511 ? dialog_vars.exit_label
518 return (dialog_vars.extra_label != NULL)
519 ? dialog_vars.extra_label
526 return (dialog_vars.help_label != NULL)
527 ? dialog_vars.help_label
532 * Return a list of button labels.
540 if (dialog_vars.extra_button) {
541 dlg_save_vars(&save);
542 dialog_vars.nocancel = TRUE;
543 result = dlg_ok_labels();
544 dlg_restore_vars(&save);
546 static const char *labels[3];
549 if (!dialog_vars.nook)
550 labels[n++] = my_exit_label();
551 if (dialog_vars.help_button)
552 labels[n++] = my_help_label();
554 labels[n++] = my_exit_label();
563 * Map the given button index for dlg_exit_label() into our exit-code.
566 dlg_exit_buttoncode(int button)
571 dlg_save_vars(&save);
572 dialog_vars.nocancel = TRUE;
574 result = dlg_ok_buttoncode(button);
576 dlg_restore_vars(&save);
584 static const char *labels[4];
587 labels[n++] = my_ok_label();
588 if (dialog_vars.extra_button)
589 labels[n++] = my_extra_label();
590 if (dialog_vars.help_button)
591 labels[n++] = my_help_label();
597 * Return a list of button labels for the OK/Cancel group.
602 static const char *labels[5];
605 if (!dialog_vars.nook)
606 labels[n++] = my_ok_label();
607 if (dialog_vars.extra_button)
608 labels[n++] = my_extra_label();
609 if (!dialog_vars.nocancel)
610 labels[n++] = my_cancel_label();
611 if (dialog_vars.help_button)
612 labels[n++] = my_help_label();
618 * Map the given button index for dlg_ok_labels() into our exit-code
621 dlg_ok_buttoncode(int button)
623 int result = DLG_EXIT_ERROR;
624 int n = !dialog_vars.nook;
626 if (!dialog_vars.nook && (button <= 0)) {
627 result = DLG_EXIT_OK;
628 } else if (dialog_vars.extra_button && (button == n++)) {
629 result = DLG_EXIT_EXTRA;
630 } else if (!dialog_vars.nocancel && (button == n++)) {
631 result = DLG_EXIT_CANCEL;
632 } else if (dialog_vars.help_button && (button == n)) {
633 result = DLG_EXIT_HELP;
635 dlg_trace_msg("# dlg_ok_buttoncode(%d) = %d\n", button, result);
640 * Given that we're using dlg_ok_labels() to list buttons, find the next index
641 * in the list of buttons. The 'extra' parameter if negative provides a way to
642 * enumerate extra active areas on the widget.
645 dlg_next_ok_buttonindex(int current, int extra)
647 int result = current + 1;
650 && dlg_ok_buttoncode(result) < 0)
656 * Similarly, find the previous button index.
659 dlg_prev_ok_buttonindex(int current, int extra)
661 int result = current - 1;
663 if (result < extra) {
664 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
672 * Find the button-index for the "OK" or "Cancel" button, according to
673 * whether --defaultno is given. If --nocancel was given, we always return
674 * the index for the first button (usually "OK" unless --nook was used).
677 dlg_defaultno_button(void)
681 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
682 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
685 dlg_trace_msg("# dlg_defaultno_button() = %d\n", result);
690 * Find the button-index for a button named with --default-button. If the
691 * option was not specified, or if the selected button does not exist, return
692 * the index of the first button (usually "OK" unless --nook was used).
695 dlg_default_button(void)
700 if (dialog_vars.default_button >= 0) {
701 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
702 if (n == dialog_vars.default_button) {
708 dlg_trace_msg("# dlg_default_button() = %d\n", result);
713 * Return a list of buttons for Yes/No labels.
720 if (dialog_vars.extra_button) {
721 result = dlg_ok_labels();
723 static const char *labels[4];
726 labels[n++] = my_yes_label();
727 labels[n++] = my_no_label();
728 if (dialog_vars.help_button)
729 labels[n++] = my_help_label();
739 * Map the given button index for dlg_yes_labels() into our exit-code.
742 dlg_yes_buttoncode(int button)
744 int result = DLG_EXIT_ERROR;
746 if (dialog_vars.extra_button) {
747 result = dlg_ok_buttoncode(button);
748 } else if (button == 0) {
749 result = DLG_EXIT_OK;
750 } else if (button == 1) {
751 result = DLG_EXIT_CANCEL;
752 } else if (button == 2 && dialog_vars.help_button) {
753 result = DLG_EXIT_HELP;
760 * Return the next index in labels[];
763 dlg_next_button(const char **labels, int button)
768 if (labels[button + 1] != 0) {
777 * Return the previous index in labels[];
780 dlg_prev_button(const char **labels, int button)
782 if (button > MIN_BUTTON) {
788 while (labels[button + 1] != 0)