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)