dhcpcd: update README.DRAGONFLY
[dragonfly.git] / contrib / dialog / buttons.c
blob355ef63bf76108af534610b021f6552c85f18e0c
1 /*
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>
25 #include <dlg_keys.h>
27 #define MIN_BUTTON (-dialog_state.visit_cols)
28 #define CHR_BUTTON (!dialog_state.plain_buttons)
30 static void
31 center_label(char *buffer, int longest, const char *label)
33 int len = dlg_count_columns(label);
34 int right = 0;
36 *buffer = 0;
37 if (len < longest) {
38 int left = (longest - len) / 2;
39 right = (longest - len - left);
40 if (left > 0)
41 sprintf(buffer, "%*s", left, " ");
43 strcat(buffer, label);
44 if (right > 0)
45 sprintf(buffer + strlen(buffer), "%*s", right, " ");
49 * Parse a multibyte character out of the string, set it past the parsed
50 * character.
52 static int
53 string_to_char(const char **stringp)
55 int result;
56 #ifdef USE_WIDE_CURSES
57 const char *string = *stringp;
58 size_t have = strlen(string);
59 size_t len;
60 wchar_t cmp2[2];
61 mbstate_t state;
63 memset(&state, 0, sizeof(state));
64 len = mbrlen(string, have, &state);
66 if ((int) len > 0 && len <= have) {
67 size_t check;
69 memset(&state, 0, sizeof(state));
70 memset(cmp2, 0, sizeof(cmp2));
71 check = mbrtowc(cmp2, string, len, &state);
72 if ((int) check <= 0)
73 cmp2[0] = 0;
74 *stringp += len;
75 } else {
76 cmp2[0] = UCH(*string);
77 *stringp += 1;
79 result = cmp2[0];
80 #else
81 const char *string = *stringp;
82 result = UCH(*string);
83 *stringp += 1;
84 #endif
85 return result;
88 static size_t
89 count_labels(const char **labels)
91 size_t result = 0;
92 if (labels != 0) {
93 while (*labels++ != 0) {
94 ++result;
97 return result;
101 * Check if the latest key should be added to the hotkey list.
103 static int
104 was_hotkey(int this_key, int *used_keys, size_t next)
106 int result = FALSE;
108 if (next != 0) {
109 size_t n;
110 for (n = 0; n < next; ++n) {
111 if (used_keys[n] == this_key) {
112 result = TRUE;
113 break;
117 return result;
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
124 * an alternate.
126 * This allocates data which must be freed by the caller.
128 static int *
129 get_hotkeys(const char **labels)
131 int *result = 0;
132 size_t count = count_labels(labels);
134 if ((result = dlg_calloc(int, count + 1)) != 0) {
135 size_t n;
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);
141 int i;
143 for (i = 0; i < limit; ++i) {
144 int first = indx[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);
152 #endif
153 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
154 result[n] = check;
155 break;
160 return result;
163 typedef enum {
164 sFIND_KEY = 0
165 ,sHAVE_KEY = 1
166 ,sHAD_KEY = 2
167 } HOTKEY;
170 * Print a button
172 static void
173 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
175 int i;
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
188 ? button_active_attr
189 : button_inactive_attr);
190 (void) waddstr(win, "<");
191 dlg_attrset(win, label_attr);
192 for (i = 0; i < limit; ++i) {
193 int check;
194 int first = indx[i];
195 int last = indx[i + 1];
197 switch (state) {
198 case sFIND_KEY:
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);
205 #endif
206 if (check == hotkey) {
207 dlg_attrset(win, key_attr);
208 state = sHAVE_KEY;
210 break;
211 case sHAVE_KEY:
212 dlg_attrset(win, label_attr);
213 state = sHAD_KEY;
214 break;
215 default:
216 break;
218 waddnstr(win, label + first, last - first);
220 dlg_attrset(win, selected
221 ? button_active_attr
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)
235 int result = 0;
236 while (*labels++ != 0)
237 ++result;
238 return result;
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
245 void
246 dlg_button_sizes(const char **labels,
247 int vertical,
248 int *longest,
249 int *length)
251 int n;
253 *length = 0;
254 *longest = 0;
255 for (n = 0; labels[n] != 0; n++) {
256 if (vertical) {
257 *length += 1;
258 *longest = 1;
259 } else {
260 int len = dlg_count_columns(labels[n]);
261 if (len > *longest)
262 *longest = len;
263 *length += len;
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);
272 if (!vertical)
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);
283 int longest;
284 int length;
285 int result;
287 *margin = 0;
288 if (count != 0) {
289 int unused;
290 int used;
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)
298 *gap = 1;
299 *margin = *gap;
300 } else {
301 *margin = *gap * 2;
303 *step = *gap + (used + count - 1) / count;
304 result = (*gap > 0) && (unused >= 0);
305 } else {
306 result = 0;
308 return result;
312 * Make sure there is enough space for the buttons
314 void
315 dlg_button_layout(const char **labels, int *limit)
317 int gap, margin, step;
319 if (labels != 0 && dlg_button_count(labels)) {
320 int width = 1;
322 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
323 ++width;
324 width += (4 * MARGIN);
325 if (width > COLS)
326 width = COLS;
327 if (width > *limit)
328 *limit = width;
333 * Print a list of buttons at the given position.
335 void
336 dlg_draw_buttons(WINDOW *win,
337 int y, int x,
338 const char **labels,
339 int selected,
340 int vertical,
341 int limit)
343 chtype save = dlg_get_attrs(win);
344 int step = 0;
345 int length;
346 int longest;
347 int final_x;
348 int final_y;
349 int gap;
350 int margin;
351 size_t need;
353 dlg_mouse_setbase(getbegx(win), getbegy(win));
355 getyx(win, final_y, final_x);
357 dlg_button_sizes(labels, vertical, &longest, &length);
359 if (vertical) {
360 y += 1;
361 step = 1;
362 } else {
363 dlg_button_x_step(labels, limit, &gap, &margin, &step);
364 x += margin;
368 * Allocate a buffer big enough for any label.
370 need = (size_t) longest;
371 if (need != 0) {
372 char *buffer;
373 int n;
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");
385 * Draw the labels.
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,
392 y, x,
393 (selected == n) || (n == 0 && selected < 0));
394 if (selected == n)
395 getyx(win, final_y, final_x);
397 if (vertical) {
398 if ((y += step) > limit)
399 break;
400 } else {
401 if ((x += step) > limit)
402 break;
405 (void) wmove(win, final_y, final_x);
406 wrefresh(win);
407 dlg_attrset(win, save);
408 free(buffer);
409 free(hotkeys);
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
416 * character.
419 dlg_match_char(int ch, const char *string)
421 if (!dialog_vars.no_hot_list) {
422 if (string != 0) {
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)) {
427 return TRUE;
429 #else
430 if (ch > 0 && ch < 256) {
431 if (dlg_toupper(ch) == dlg_toupper(cmp2))
432 return TRUE;
434 #endif
437 return FALSE;
441 * Find the first uppercase character in the label, which we may use for an
442 * abbreviation.
445 dlg_button_to_char(const char *label)
447 int cmp = -1;
449 while (*label != 0) {
450 int ch = string_to_char(&label);
451 if (dlg_isupper(ch)) {
452 cmp = ch;
453 break;
456 return cmp;
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;
469 if (labels != 0) {
470 int *hotkeys = get_hotkeys(labels);
472 ch = (int) dlg_toupper(dlg_last_getc());
474 if (hotkeys != 0) {
475 int j;
477 for (j = 0; labels[j] != 0; ++j) {
478 if (ch == hotkeys[j]) {
479 dlg_flush_getc();
480 result = j;
481 break;
484 free(hotkeys);
488 return result;
491 static const char *
492 my_yes_label(void)
494 return (dialog_vars.yes_label != NULL)
495 ? dialog_vars.yes_label
496 : _("Yes");
499 static const char *
500 my_no_label(void)
502 return (dialog_vars.no_label != NULL)
503 ? dialog_vars.no_label
504 : _("No");
507 static const char *
508 my_ok_label(void)
510 return (dialog_vars.ok_label != NULL)
511 ? dialog_vars.ok_label
512 : _("OK");
515 static const char *
516 my_cancel_label(void)
518 return (dialog_vars.cancel_label != NULL)
519 ? dialog_vars.cancel_label
520 : _("Cancel");
523 static const char *
524 my_exit_label(void)
526 return (dialog_vars.exit_label != NULL)
527 ? dialog_vars.exit_label
528 : _("EXIT");
531 static const char *
532 my_extra_label(void)
534 return (dialog_vars.extra_label != NULL)
535 ? dialog_vars.extra_label
536 : _("Extra");
539 static const char *
540 my_help_label(void)
542 return (dialog_vars.help_label != NULL)
543 ? dialog_vars.help_label
544 : _("Help");
548 * Return a list of button labels.
550 const char **
551 dlg_exit_label(void)
553 const char **result;
554 DIALOG_VARS save;
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);
561 } else {
562 static const char *labels[3];
563 int n = 0;
565 if (!dialog_vars.nook)
566 labels[n++] = my_exit_label();
567 if (dialog_vars.help_button)
568 labels[n++] = my_help_label();
569 if (n == 0)
570 labels[n++] = my_exit_label();
571 labels[n] = 0;
573 result = labels;
575 return result;
579 * Map the given button index for dlg_exit_label() into our exit-code.
582 dlg_exit_buttoncode(int button)
584 int result;
585 DIALOG_VARS save;
587 dlg_save_vars(&save);
588 dialog_vars.nocancel = TRUE;
590 result = dlg_ok_buttoncode(button);
592 dlg_restore_vars(&save);
594 return result;
597 static const char **
598 finish_ok_label(const char **labels, int n)
600 if (n == 0) {
601 labels[n++] = my_ok_label();
602 dialog_vars.nook = FALSE;
603 DLG_TRACE(("# ignore --nook, since at least one button is needed\n"));
606 labels[n] = NULL;
607 return labels;
611 * Return a list of button labels for the OK (no Cancel) group, used in msgbox
612 * and progressbox.
614 const char **
615 dlg_ok_label(void)
617 static const char *labels[4];
618 int n = 0;
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.
634 const char **
635 dlg_ok_labels(void)
637 static const char *labels[5];
638 int n = 0;
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)));
672 return 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;
685 if (current >= 0
686 && dlg_ok_buttoncode(result) < 0)
687 result = extra;
688 return result;
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) {
704 return 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)
715 int result = 0;
717 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
718 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
719 ++result;
721 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
722 return 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)
733 int result = 0;
735 if (dialog_vars.default_button >= 0) {
736 int i, n;
738 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
739 if (n == dialog_vars.default_button) {
740 result = i;
741 break;
745 DLG_TRACE(("# dlg_default_button() = %d\n", result));
746 return result;
750 * Return a list of buttons for Yes/No labels.
752 const char **
753 dlg_yes_labels(void)
755 static const char *labels[5];
756 int n = 0;
757 const char **result;
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();
765 labels[n] = NULL;
767 result = labels;
769 return result;
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;
790 return result;
794 * Return the next index in labels[];
797 dlg_next_button(const char **labels, int button)
799 if (button < -1)
800 button = -1;
802 if (labels[button + 1] != 0) {
803 ++button;
804 } else {
805 button = MIN_BUTTON;
807 return button;
811 * Return the previous index in labels[];
814 dlg_prev_button(const char **labels, int button)
816 if (button > MIN_BUTTON) {
817 --button;
818 } else {
819 if (button < -1)
820 button = -1;
822 while (labels[button + 1] != 0)
823 ++button;
825 return button;