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)