1 /*
2 * $Id: buttons.c,v 1.109 2022/04/05 23:45:54 tom Exp $
3 *
4 * buttons.c -- draw buttons, e.g., OK/Cancel
5 *
6 * Copyright 2000-2021,2022 Thomas E. Dickey
7 *
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.
11 *
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.
16 *
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.
22 */
23
24 #include <dlg_internals.h>
25 #include <dlg_keys.h>
26
27 #define MIN_BUTTON (-dialog_state.visit_cols)
28 #define CHR_BUTTON (!dialog_state.plain_buttons)
29
30 static void
center_label(char * buffer,int longest,const char * label)31 center_label(char *buffer, int longest, const char *label)
32 {
33 int len = dlg_count_columns(label);
34 int right = 0;
35
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, " ");
42 }
43 strcat(buffer, label);
44 if (right > 0)
45 sprintf(buffer + strlen(buffer), "%*s", right, " ");
46 }
47
48 /*
49 * Parse a multibyte character out of the string, set it past the parsed
50 * character.
51 */
52 static int
string_to_char(const char ** stringp)53 string_to_char(const char **stringp)
54 {
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;
62
63 memset(&state, 0, sizeof(state));
64 len = mbrlen(string, have, &state);
65
66 if ((int) len > 0 && len <= have) {
67 size_t check;
68
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;
78 }
79 result = cmp2[0];
80 #else
81 const char *string = *stringp;
82 result = UCH(*string);
83 *stringp += 1;
84 #endif
85 return result;
86 }
87
88 static size_t
count_labels(const char ** labels)89 count_labels(const char **labels)
90 {
91 size_t result = 0;
92 if (labels != 0) {
93 while (*labels++ != 0) {
94 ++result;
95 }
96 }
97 return result;
98 }
99
100 /*
101 * Check if the latest key should be added to the hotkey list.
102 */
103 static int
was_hotkey(int this_key,int * used_keys,size_t next)104 was_hotkey(int this_key, int *used_keys, size_t next)
105 {
106 int result = FALSE;
107
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;
114 }
115 }
116 }
117 return result;
118 }
119
120 /*
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.
125 *
126 * This allocates data which must be freed by the caller.
127 */
128 static int *
get_hotkeys(const char ** labels)129 get_hotkeys(const char **labels)
130 {
131 int *result = 0;
132 size_t count = count_labels(labels);
133
134 if ((result = dlg_calloc(int, count + 1)) != 0) {
135 size_t n;
136
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;
142
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);
151 }
152 #endif
153 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
154 result[n] = check;
155 break;
156 }
157 }
158 }
159 }
160 return result;
161 }
162
163 typedef enum {
164 sFIND_KEY = 0
165 ,sHAVE_KEY = 1
166 ,sHAD_KEY = 2
167 } HOTKEY;
168
169 /*
170 * Print a button
171 */
172 static void
print_button(WINDOW * win,char * label,int hotkey,int y,int x,int selected)173 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
174 {
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);
185
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];
196
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);
204 }
205 #endif
206 if (check == hotkey) {
207 dlg_attrset(win, key_attr);
208 state = sHAVE_KEY;
209 }
210 break;
211 case sHAVE_KEY:
212 dlg_attrset(win, label_attr);
213 state = sHAD_KEY;
214 break;
215 default:
216 break;
217 }
218 waddnstr(win, label + first, last - first);
219 }
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);
226 }
227 }
228
229 /*
230 * Count the buttons in the list.
231 */
232 int
dlg_button_count(const char ** labels)233 dlg_button_count(const char **labels)
234 {
235 int result = 0;
236 while (*labels++ != 0)
237 ++result;
238 return result;
239 }
240
241 /*
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
244 */
245 void
dlg_button_sizes(const char ** labels,int vertical,int * longest,int * length)246 dlg_button_sizes(const char **labels,
247 int vertical,
248 int *longest,
249 int *length)
250 {
251 int n;
252
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;
264 }
265 }
266 /*
267 * If we can, make all of the buttons the same size. This is only optional
268 * for buttons laid out horizontally.
269 */
270 if (*longest < 6 - (*longest & 1))
271 *longest = 6 - (*longest & 1);
272 if (!vertical)
273 *length = *longest * n;
274 }
275
276 /*
277 * Compute the size of the button array.
278 */
279 int
dlg_button_x_step(const char ** labels,int limit,int * gap,int * margin,int * step)280 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
281 {
282 int count = dlg_button_count(labels);
283 int longest;
284 int length;
285 int result;
286
287 *margin = 0;
288 if (count != 0) {
289 int unused;
290 int used;
291
292 dlg_button_sizes(labels, FALSE, &longest, &length);
293 used = (length + (count * 2));
294 unused = limit - used;
295
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;
302 }
303 *step = *gap + (used + count - 1) / count;
304 result = (*gap > 0) && (unused >= 0);
305 } else {
306 result = 0;
307 }
308 return result;
309 }
310
311 /*
312 * Make sure there is enough space for the buttons
313 */
314 void
dlg_button_layout(const char ** labels,int * limit)315 dlg_button_layout(const char **labels, int *limit)
316 {
317 int gap, margin, step;
318
319 if (labels != 0 && dlg_button_count(labels)) {
320 int width = 1;
321
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;
329 }
330 }
331
332 /*
333 * Print a list of buttons at the given position.
334 */
335 void
dlg_draw_buttons(WINDOW * win,int y,int x,const char ** labels,int selected,int vertical,int limit)336 dlg_draw_buttons(WINDOW *win,
337 int y, int x,
338 const char **labels,
339 int selected,
340 int vertical,
341 int limit)
342 {
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;
352
353 dlg_mouse_setbase(getbegx(win), getbegy(win));
354
355 getyx(win, final_y, final_x);
356
357 dlg_button_sizes(labels, vertical, &longest, &length);
358
359 if (vertical) {
360 y += 1;
361 step = 1;
362 } else {
363 dlg_button_x_step(labels, limit, &gap, &margin, &step);
364 x += margin;
365 }
366
367 /*
368 * Allocate a buffer big enough for any label.
369 */
370 need = (size_t) longest;
371 if (need != 0) {
372 char *buffer;
373 int n;
374 int *hotkeys = get_hotkeys(labels);
375
376 assert_ptr(hotkeys, "dlg_draw_buttons");
377
378 for (n = 0; labels[n] != 0; ++n) {
379 need += strlen(labels[n]) + 1;
380 }
381 buffer = dlg_malloc(char, need);
382 assert_ptr(buffer, "dlg_draw_buttons");
383
384 /*
385 * Draw the labels.
386 */
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);
396
397 if (vertical) {
398 if ((y += step) > limit)
399 break;
400 } else {
401 if ((x += step) > limit)
402 break;
403 }
404 }
405 (void) wmove(win, final_y, final_x);
406 wrefresh(win);
407 dlg_attrset(win, save);
408 free(buffer);
409 free(hotkeys);
410 }
411 }
412
413 /*
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.
417 */
418 int
dlg_match_char(int ch,const char * string)419 dlg_match_char(int ch, const char *string)
420 {
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;
428 }
429 #else
430 if (ch > 0 && ch < 256) {
431 if (dlg_toupper(ch) == dlg_toupper(cmp2))
432 return TRUE;
433 }
434 #endif
435 }
436 }
437 return FALSE;
438 }
439
440 /*
441 * Find the first uppercase character in the label, which we may use for an
442 * abbreviation.
443 */
444 int
dlg_button_to_char(const char * label)445 dlg_button_to_char(const char *label)
446 {
447 int cmp = -1;
448
449 while (*label != 0) {
450 int ch = string_to_char(&label);
451 if (dlg_isupper(ch)) {
452 cmp = ch;
453 break;
454 }
455 }
456 return cmp;
457 }
458
459 /*
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.
463 */
464 int
dlg_char_to_button(int ch,const char ** labels)465 dlg_char_to_button(int ch, const char **labels)
466 {
467 int result = DLG_EXIT_UNKNOWN;
468
469 if (labels != 0) {
470 int *hotkeys = get_hotkeys(labels);
471
472 ch = (int) dlg_toupper(dlg_last_getc());
473
474 if (hotkeys != 0) {
475 int j;
476
477 for (j = 0; labels[j] != 0; ++j) {
478 if (ch == hotkeys[j]) {
479 dlg_flush_getc();
480 result = j;
481 break;
482 }
483 }
484 free(hotkeys);
485 }
486 }
487
488 return result;
489 }
490
491 static const char *
my_yes_label(void)492 my_yes_label(void)
493 {
494 return (dialog_vars.yes_label != NULL)
495 ? dialog_vars.yes_label
496 : _("Yes");
497 }
498
499 static const char *
my_no_label(void)500 my_no_label(void)
501 {
502 return (dialog_vars.no_label != NULL)
503 ? dialog_vars.no_label
504 : _("No");
505 }
506
507 static const char *
my_ok_label(void)508 my_ok_label(void)
509 {
510 return (dialog_vars.ok_label != NULL)
511 ? dialog_vars.ok_label
512 : _("OK");
513 }
514
515 static const char *
my_cancel_label(void)516 my_cancel_label(void)
517 {
518 return (dialog_vars.cancel_label != NULL)
519 ? dialog_vars.cancel_label
520 : _("Cancel");
521 }
522
523 static const char *
my_exit_label(void)524 my_exit_label(void)
525 {
526 return (dialog_vars.exit_label != NULL)
527 ? dialog_vars.exit_label
528 : _("EXIT");
529 }
530
531 static const char *
my_extra_label(void)532 my_extra_label(void)
533 {
534 return (dialog_vars.extra_label != NULL)
535 ? dialog_vars.extra_label
536 : _("Extra");
537 }
538
539 static const char *
my_help_label(void)540 my_help_label(void)
541 {
542 return (dialog_vars.help_label != NULL)
543 ? dialog_vars.help_label
544 : _("Help");
545 }
546
547 /*
548 * Return a list of button labels.
549 */
550 const char **
dlg_exit_label(void)551 dlg_exit_label(void)
552 {
553 const char **result;
554 DIALOG_VARS save;
555
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;
564
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;
572
573 result = labels;
574 }
575 return result;
576 }
577
578 /*
579 * Map the given button index for dlg_exit_label() into our exit-code.
580 */
581 int
dlg_exit_buttoncode(int button)582 dlg_exit_buttoncode(int button)
583 {
584 int result;
585 DIALOG_VARS save;
586
587 dlg_save_vars(&save);
588 dialog_vars.nocancel = TRUE;
589
590 result = dlg_ok_buttoncode(button);
591
592 dlg_restore_vars(&save);
593
594 return result;
595 }
596
597 static const char **
finish_ok_label(const char ** labels,int n)598 finish_ok_label(const char **labels, int n)
599 {
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"));
604 }
605
606 labels[n] = NULL;
607 return labels;
608 }
609
610 /*
611 * Return a list of button labels for the OK (no Cancel) group, used in msgbox
612 * and progressbox.
613 */
614 const char **
dlg_ok_label(void)615 dlg_ok_label(void)
616 {
617 static const char *labels[4];
618 int n = 0;
619
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();
626
627 return finish_ok_label(labels, n);
628 }
629
630 /*
631 * Return a list of button labels for the OK/Cancel group, used in most widgets
632 * that select an option or data.
633 */
634 const char **
dlg_ok_labels(void)635 dlg_ok_labels(void)
636 {
637 static const char *labels[5];
638 int n = 0;
639
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();
648
649 return finish_ok_label(labels, n);
650 }
651
652 /*
653 * Map the given button index for dlg_ok_labels() into our exit-code
654 */
655 int
dlg_ok_buttoncode(int button)656 dlg_ok_buttoncode(int button)
657 {
658 int result = DLG_EXIT_ERROR;
659 int n = !dialog_vars.nook;
660
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;
669 }
670 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n",
671 button, result, dlg_exitcode2s(result)));
672 return result;
673 }
674
675 /*
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.
679 */
680 int
dlg_next_ok_buttonindex(int current,int extra)681 dlg_next_ok_buttonindex(int current, int extra)
682 {
683 int result = current + 1;
684
685 if (current >= 0
686 && dlg_ok_buttoncode(result) < 0)
687 result = extra;
688 return result;
689 }
690
691 /*
692 * Similarly, find the previous button index.
693 */
694 int
dlg_prev_ok_buttonindex(int current,int extra)695 dlg_prev_ok_buttonindex(int current, int extra)
696 {
697 int result = current - 1;
698
699 if (result < extra) {
700 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
701 ;
702 }
703 }
704 return result;
705 }
706
707 /*
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).
711 */
712 int
dlg_defaultno_button(void)713 dlg_defaultno_button(void)
714 {
715 int result = 0;
716
717 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
718 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
719 ++result;
720 }
721 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
722 return result;
723 }
724
725 /*
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).
729 */
730 int
dlg_default_button(void)731 dlg_default_button(void)
732 {
733 int result = 0;
734
735 if (dialog_vars.default_button >= 0) {
736 int i, n;
737
738 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
739 if (n == dialog_vars.default_button) {
740 result = i;
741 break;
742 }
743 }
744 }
745 DLG_TRACE(("# dlg_default_button() = %d\n", result));
746 return result;
747 }
748
749 /*
750 * Return a list of buttons for Yes/No labels.
751 */
752 const char **
dlg_yes_labels(void)753 dlg_yes_labels(void)
754 {
755 static const char *labels[5];
756 int n = 0;
757 const char **result;
758
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;
766
767 result = labels;
768
769 return result;
770 }
771
772 /*
773 * Map the given button index for dlg_yes_labels() into our exit-code.
774 */
775 int
dlg_yes_buttoncode(int button)776 dlg_yes_buttoncode(int button)
777 {
778 int result = DLG_EXIT_ERROR;
779
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;
788 }
789
790 return result;
791 }
792
793 /*
794 * Return the next index in labels[];
795 */
796 int
dlg_next_button(const char ** labels,int button)797 dlg_next_button(const char **labels, int button)
798 {
799 if (button < -1)
800 button = -1;
801
802 if (labels[button + 1] != 0) {
803 ++button;
804 } else {
805 button = MIN_BUTTON;
806 }
807 return button;
808 }
809
810 /*
811 * Return the previous index in labels[];
812 */
813 int
dlg_prev_button(const char ** labels,int button)814 dlg_prev_button(const char **labels, int button)
815 {
816 if (button > MIN_BUTTON) {
817 --button;
818 } else {
819 if (button < -1)
820 button = -1;
821
822 while (labels[button + 1] != 0)
823 ++button;
824 }
825 return button;
826 }
827