1 /*
2 * $Id: buttons.c,v 1.106 2021/01/17 17:03:16 tom Exp $
3 *
4 * buttons.c -- draw buttons, e.g., OK/Cancel
5 *
6 * Copyright 2000-2020,2021 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 <dialog.h>
25 #include <dlg_keys.h>
26
27 #ifdef NEED_WCHAR_H
28 #include <wchar.h>
29 #endif
30
31 #define MIN_BUTTON (-dialog_state.visit_cols)
32 #define CHR_BUTTON (!dialog_state.plain_buttons)
33
34 static void
center_label(char * buffer,int longest,const char * label)35 center_label(char *buffer, int longest, const char *label)
36 {
37 int len = dlg_count_columns(label);
38 int right = 0;
39
40 *buffer = 0;
41 if (len < longest) {
42 int left = (longest - len) / 2;
43 right = (longest - len - left);
44 if (left > 0)
45 sprintf(buffer, "%*s", left, " ");
46 }
47 strcat(buffer, label);
48 if (right > 0)
49 sprintf(buffer + strlen(buffer), "%*s", right, " ");
50 }
51
52 /*
53 * Parse a multibyte character out of the string, set it past the parsed
54 * character.
55 */
56 static int
string_to_char(const char ** stringp)57 string_to_char(const char **stringp)
58 {
59 int result;
60 #ifdef USE_WIDE_CURSES
61 const char *string = *stringp;
62 size_t have = strlen(string);
63 size_t len;
64 wchar_t cmp2[2];
65 mbstate_t state;
66
67 memset(&state, 0, sizeof(state));
68 len = mbrlen(string, have, &state);
69
70 if ((int) len > 0 && len <= have) {
71 size_t check;
72
73 memset(&state, 0, sizeof(state));
74 memset(cmp2, 0, sizeof(cmp2));
75 check = mbrtowc(cmp2, string, len, &state);
76 if ((int) check <= 0)
77 cmp2[0] = 0;
78 *stringp += len;
79 } else {
80 cmp2[0] = UCH(*string);
81 *stringp += 1;
82 }
83 result = cmp2[0];
84 #else
85 const char *string = *stringp;
86 result = UCH(*string);
87 *stringp += 1;
88 #endif
89 return result;
90 }
91
92 static size_t
count_labels(const char ** labels)93 count_labels(const char **labels)
94 {
95 size_t result = 0;
96 if (labels != 0) {
97 while (*labels++ != 0) {
98 ++result;
99 }
100 }
101 return result;
102 }
103
104 /*
105 * Check if the latest key should be added to the hotkey list.
106 */
107 static int
was_hotkey(int this_key,int * used_keys,size_t next)108 was_hotkey(int this_key, int *used_keys, size_t next)
109 {
110 int result = FALSE;
111
112 if (next != 0) {
113 size_t n;
114 for (n = 0; n < next; ++n) {
115 if (used_keys[n] == this_key) {
116 result = TRUE;
117 break;
118 }
119 }
120 }
121 return result;
122 }
123
124 /*
125 * Determine the hot-keys for a set of button-labels. Normally these are
126 * the first uppercase character in each label. However, if more than one
127 * button has the same first-uppercase, then we will (attempt to) look for
128 * an alternate.
129 *
130 * This allocates data which must be freed by the caller.
131 */
132 static int *
get_hotkeys(const char ** labels)133 get_hotkeys(const char **labels)
134 {
135 int *result = 0;
136 size_t count = count_labels(labels);
137
138 if ((result = dlg_calloc(int, count + 1)) != 0) {
139 size_t n;
140
141 for (n = 0; n < count; ++n) {
142 const char *label = labels[n];
143 const int *indx = dlg_index_wchars(label);
144 int limit = dlg_count_wchars(label);
145 int i;
146
147 for (i = 0; i < limit; ++i) {
148 int first = indx[i];
149 int check = UCH(label[first]);
150 #ifdef USE_WIDE_CURSES
151 int last = indx[i + 1];
152 if ((last - first) != 1) {
153 const char *temp = (label + first);
154 check = string_to_char(&temp);
155 }
156 #endif
157 if (dlg_isupper(check) && !was_hotkey(check, result, n)) {
158 result[n] = check;
159 break;
160 }
161 }
162 }
163 }
164 return result;
165 }
166
167 typedef enum {
168 sFIND_KEY = 0
169 ,sHAVE_KEY = 1
170 ,sHAD_KEY = 2
171 } HOTKEY;
172
173 /*
174 * Print a button
175 */
176 static void
print_button(WINDOW * win,char * label,int hotkey,int y,int x,int selected)177 print_button(WINDOW *win, char *label, int hotkey, int y, int x, int selected)
178 {
179 int i;
180 HOTKEY state = sFIND_KEY;
181 const int *indx = dlg_index_wchars(label);
182 int limit = dlg_count_wchars(label);
183 chtype key_attr = (selected
184 ? button_key_active_attr
185 : button_key_inactive_attr);
186 chtype label_attr = (selected
187 ? button_label_active_attr
188 : button_label_inactive_attr);
189
190 (void) wmove(win, y, x);
191 dlg_attrset(win, selected
192 ? button_active_attr
193 : button_inactive_attr);
194 (void) waddstr(win, "<");
195 dlg_attrset(win, label_attr);
196 for (i = 0; i < limit; ++i) {
197 int check;
198 int first = indx[i];
199 int last = indx[i + 1];
200
201 switch (state) {
202 case sFIND_KEY:
203 check = UCH(label[first]);
204 #ifdef USE_WIDE_CURSES
205 if ((last - first) != 1) {
206 const char *temp = (label + first);
207 check = string_to_char(&temp);
208 }
209 #endif
210 if (check == hotkey) {
211 dlg_attrset(win, key_attr);
212 state = sHAVE_KEY;
213 }
214 break;
215 case sHAVE_KEY:
216 dlg_attrset(win, label_attr);
217 state = sHAD_KEY;
218 break;
219 default:
220 break;
221 }
222 waddnstr(win, label + first, last - first);
223 }
224 dlg_attrset(win, selected
225 ? button_active_attr
226 : button_inactive_attr);
227 (void) waddstr(win, ">");
228 if (!dialog_vars.cursor_off_label) {
229 (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
230 }
231 }
232
233 /*
234 * Count the buttons in the list.
235 */
236 int
dlg_button_count(const char ** labels)237 dlg_button_count(const char **labels)
238 {
239 int result = 0;
240 while (*labels++ != 0)
241 ++result;
242 return result;
243 }
244
245 /*
246 * Compute the size of the button array in columns. Return the total number of
247 * columns in *length, and the longest button's columns in *longest
248 */
249 void
dlg_button_sizes(const char ** labels,int vertical,int * longest,int * length)250 dlg_button_sizes(const char **labels,
251 int vertical,
252 int *longest,
253 int *length)
254 {
255 int n;
256
257 *length = 0;
258 *longest = 0;
259 for (n = 0; labels[n] != 0; n++) {
260 if (vertical) {
261 *length += 1;
262 *longest = 1;
263 } else {
264 int len = dlg_count_columns(labels[n]);
265 if (len > *longest)
266 *longest = len;
267 *length += len;
268 }
269 }
270 /*
271 * If we can, make all of the buttons the same size. This is only optional
272 * for buttons laid out horizontally.
273 */
274 if (*longest < 6 - (*longest & 1))
275 *longest = 6 - (*longest & 1);
276 if (!vertical)
277 *length = *longest * n;
278 }
279
280 /*
281 * Compute the size of the button array.
282 */
283 int
dlg_button_x_step(const char ** labels,int limit,int * gap,int * margin,int * step)284 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
285 {
286 int count = dlg_button_count(labels);
287 int longest;
288 int length;
289 int result;
290
291 *margin = 0;
292 if (count != 0) {
293 int unused;
294 int used;
295
296 dlg_button_sizes(labels, FALSE, &longest, &length);
297 used = (length + (count * 2));
298 unused = limit - used;
299
300 if ((*gap = unused / (count + 3)) <= 0) {
301 if ((*gap = unused / (count + 1)) <= 0)
302 *gap = 1;
303 *margin = *gap;
304 } else {
305 *margin = *gap * 2;
306 }
307 *step = *gap + (used + count - 1) / count;
308 result = (*gap > 0) && (unused >= 0);
309 } else {
310 result = 0;
311 }
312 return result;
313 }
314
315 /*
316 * Make sure there is enough space for the buttons
317 */
318 void
dlg_button_layout(const char ** labels,int * limit)319 dlg_button_layout(const char **labels, int *limit)
320 {
321 int gap, margin, step;
322
323 if (labels != 0 && dlg_button_count(labels)) {
324 int width = 1;
325
326 while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
327 ++width;
328 width += (4 * MARGIN);
329 if (width > COLS)
330 width = COLS;
331 if (width > *limit)
332 *limit = width;
333 }
334 }
335
336 /*
337 * Print a list of buttons at the given position.
338 */
339 void
dlg_draw_buttons(WINDOW * win,int y,int x,const char ** labels,int selected,int vertical,int limit)340 dlg_draw_buttons(WINDOW *win,
341 int y, int x,
342 const char **labels,
343 int selected,
344 int vertical,
345 int limit)
346 {
347 chtype save = dlg_get_attrs(win);
348 int step = 0;
349 int length;
350 int longest;
351 int final_x;
352 int final_y;
353 int gap;
354 int margin;
355 size_t need;
356
357 dlg_mouse_setbase(getbegx(win), getbegy(win));
358
359 getyx(win, final_y, final_x);
360
361 dlg_button_sizes(labels, vertical, &longest, &length);
362
363 if (vertical) {
364 y += 1;
365 step = 1;
366 } else {
367 dlg_button_x_step(labels, limit, &gap, &margin, &step);
368 x += margin;
369 }
370
371 /*
372 * Allocate a buffer big enough for any label.
373 */
374 need = (size_t) longest;
375 if (need != 0) {
376 char *buffer;
377 int n;
378 int *hotkeys = get_hotkeys(labels);
379
380 assert_ptr(hotkeys, "dlg_draw_buttons");
381
382 for (n = 0; labels[n] != 0; ++n) {
383 need += strlen(labels[n]) + 1;
384 }
385 buffer = dlg_malloc(char, need);
386 assert_ptr(buffer, "dlg_draw_buttons");
387
388 /*
389 * Draw the labels.
390 */
391 for (n = 0; labels[n] != 0; n++) {
392 center_label(buffer, longest, labels[n]);
393 mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
394 print_button(win, buffer,
395 CHR_BUTTON ? hotkeys[n] : -1,
396 y, x,
397 (selected == n) || (n == 0 && selected < 0));
398 if (selected == n)
399 getyx(win, final_y, final_x);
400
401 if (vertical) {
402 if ((y += step) > limit)
403 break;
404 } else {
405 if ((x += step) > limit)
406 break;
407 }
408 }
409 (void) wmove(win, final_y, final_x);
410 wrefresh(win);
411 dlg_attrset(win, save);
412 free(buffer);
413 free(hotkeys);
414 }
415 }
416
417 /*
418 * Match a given character against the beginning of the string, ignoring case
419 * of the given character. The matching string must begin with an uppercase
420 * character.
421 */
422 int
dlg_match_char(int ch,const char * string)423 dlg_match_char(int ch, const char *string)
424 {
425 if (!dialog_vars.no_hot_list) {
426 if (string != 0) {
427 int cmp2 = string_to_char(&string);
428 #ifdef USE_WIDE_CURSES
429 wint_t cmp1 = dlg_toupper(ch);
430 if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
431 return TRUE;
432 }
433 #else
434 if (ch > 0 && ch < 256) {
435 if (dlg_toupper(ch) == dlg_toupper(cmp2))
436 return TRUE;
437 }
438 #endif
439 }
440 }
441 return FALSE;
442 }
443
444 /*
445 * Find the first uppercase character in the label, which we may use for an
446 * abbreviation.
447 */
448 int
dlg_button_to_char(const char * label)449 dlg_button_to_char(const char *label)
450 {
451 int cmp = -1;
452
453 while (*label != 0) {
454 int ch = string_to_char(&label);
455 if (dlg_isupper(ch)) {
456 cmp = ch;
457 break;
458 }
459 }
460 return cmp;
461 }
462
463 /*
464 * Given a list of button labels, and a character which may be the abbreviation
465 * for one, find it, if it exists. An abbreviation will be the first character
466 * which happens to be capitalized in the label.
467 */
468 int
dlg_char_to_button(int ch,const char ** labels)469 dlg_char_to_button(int ch, const char **labels)
470 {
471 int result = DLG_EXIT_UNKNOWN;
472
473 if (labels != 0) {
474 int *hotkeys = get_hotkeys(labels);
475
476 ch = (int) dlg_toupper(dlg_last_getc());
477
478 if (hotkeys != 0) {
479 int j;
480
481 for (j = 0; labels[j] != 0; ++j) {
482 if (ch == hotkeys[j]) {
483 dlg_flush_getc();
484 result = j;
485 break;
486 }
487 }
488 free(hotkeys);
489 }
490 }
491
492 return result;
493 }
494
495 static const char *
my_yes_label(void)496 my_yes_label(void)
497 {
498 return (dialog_vars.yes_label != NULL)
499 ? dialog_vars.yes_label
500 : _("Yes");
501 }
502
503 static const char *
my_no_label(void)504 my_no_label(void)
505 {
506 return (dialog_vars.no_label != NULL)
507 ? dialog_vars.no_label
508 : _("No");
509 }
510
511 static const char *
my_ok_label(void)512 my_ok_label(void)
513 {
514 return (dialog_vars.ok_label != NULL)
515 ? dialog_vars.ok_label
516 : _("OK");
517 }
518
519 static const char *
my_cancel_label(void)520 my_cancel_label(void)
521 {
522 return (dialog_vars.cancel_label != NULL)
523 ? dialog_vars.cancel_label
524 : _("Cancel");
525 }
526
527 static const char *
my_exit_label(void)528 my_exit_label(void)
529 {
530 return (dialog_vars.exit_label != NULL)
531 ? dialog_vars.exit_label
532 : _("EXIT");
533 }
534
535 static const char *
my_extra_label(void)536 my_extra_label(void)
537 {
538 return (dialog_vars.extra_label != NULL)
539 ? dialog_vars.extra_label
540 : _("Extra");
541 }
542
543 static const char *
my_help_label(void)544 my_help_label(void)
545 {
546 return (dialog_vars.help_label != NULL)
547 ? dialog_vars.help_label
548 : _("Help");
549 }
550
551 /*
552 * Return a list of button labels.
553 */
554 const char **
dlg_exit_label(void)555 dlg_exit_label(void)
556 {
557 const char **result;
558 DIALOG_VARS save;
559
560 if (dialog_vars.extra_button) {
561 dlg_save_vars(&save);
562 dialog_vars.nocancel = TRUE;
563 result = dlg_ok_labels();
564 dlg_restore_vars(&save);
565 } else {
566 static const char *labels[3];
567 int n = 0;
568
569 if (!dialog_vars.nook)
570 labels[n++] = my_exit_label();
571 if (dialog_vars.help_button)
572 labels[n++] = my_help_label();
573 if (n == 0)
574 labels[n++] = my_exit_label();
575 labels[n] = 0;
576
577 result = labels;
578 }
579 return result;
580 }
581
582 /*
583 * Map the given button index for dlg_exit_label() into our exit-code.
584 */
585 int
dlg_exit_buttoncode(int button)586 dlg_exit_buttoncode(int button)
587 {
588 int result;
589 DIALOG_VARS save;
590
591 dlg_save_vars(&save);
592 dialog_vars.nocancel = TRUE;
593
594 result = dlg_ok_buttoncode(button);
595
596 dlg_restore_vars(&save);
597
598 return result;
599 }
600
601 static const char **
finish_ok_label(const char ** labels,int n)602 finish_ok_label(const char **labels, int n)
603 {
604 if (n == 0) {
605 labels[n++] = my_ok_label();
606 dialog_vars.nook = FALSE;
607 dlg_trace_msg("# ignore --nook, since at least one button is needed\n");
608 }
609
610 labels[n] = NULL;
611 return labels;
612 }
613
614 /*
615 * Return a list of button labels for the OK (no Cancel) group, used in msgbox
616 * and progressbox.
617 */
618 const char **
dlg_ok_label(void)619 dlg_ok_label(void)
620 {
621 static const char *labels[4];
622 int n = 0;
623
624 if (!dialog_vars.nook)
625 labels[n++] = my_ok_label();
626 if (dialog_vars.extra_button)
627 labels[n++] = my_extra_label();
628 if (dialog_vars.help_button)
629 labels[n++] = my_help_label();
630
631 return finish_ok_label(labels, n);
632 }
633
634 /*
635 * Return a list of button labels for the OK/Cancel group, used in most widgets
636 * that select an option or data.
637 */
638 const char **
dlg_ok_labels(void)639 dlg_ok_labels(void)
640 {
641 static const char *labels[5];
642 int n = 0;
643
644 if (!dialog_vars.nook)
645 labels[n++] = my_ok_label();
646 if (dialog_vars.extra_button)
647 labels[n++] = my_extra_label();
648 if (!dialog_vars.nocancel)
649 labels[n++] = my_cancel_label();
650 if (dialog_vars.help_button)
651 labels[n++] = my_help_label();
652
653 return finish_ok_label(labels, n);
654 }
655
656 /*
657 * Map the given button index for dlg_ok_labels() into our exit-code
658 */
659 int
dlg_ok_buttoncode(int button)660 dlg_ok_buttoncode(int button)
661 {
662 int result = DLG_EXIT_ERROR;
663 int n = !dialog_vars.nook;
664
665 if (!dialog_vars.nook && (button <= 0)) {
666 result = DLG_EXIT_OK;
667 } else if (dialog_vars.extra_button && (button == n++)) {
668 result = DLG_EXIT_EXTRA;
669 } else if (!dialog_vars.nocancel && (button == n++)) {
670 result = DLG_EXIT_CANCEL;
671 } else if (dialog_vars.help_button && (button == n)) {
672 result = DLG_EXIT_HELP;
673 }
674 DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n",
675 button, result, dlg_exitcode2s(result)));
676 return result;
677 }
678
679 /*
680 * Given that we're using dlg_ok_labels() to list buttons, find the next index
681 * in the list of buttons. The 'extra' parameter if negative provides a way to
682 * enumerate extra active areas on the widget.
683 */
684 int
dlg_next_ok_buttonindex(int current,int extra)685 dlg_next_ok_buttonindex(int current, int extra)
686 {
687 int result = current + 1;
688
689 if (current >= 0
690 && dlg_ok_buttoncode(result) < 0)
691 result = extra;
692 return result;
693 }
694
695 /*
696 * Similarly, find the previous button index.
697 */
698 int
dlg_prev_ok_buttonindex(int current,int extra)699 dlg_prev_ok_buttonindex(int current, int extra)
700 {
701 int result = current - 1;
702
703 if (result < extra) {
704 for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
705 ;
706 }
707 }
708 return result;
709 }
710
711 /*
712 * Find the button-index for the "OK" or "Cancel" button, according to
713 * whether --defaultno is given. If --nocancel was given, we always return
714 * the index for the first button (usually "OK" unless --nook was used).
715 */
716 int
dlg_defaultno_button(void)717 dlg_defaultno_button(void)
718 {
719 int result = 0;
720
721 if (dialog_vars.defaultno && !dialog_vars.nocancel) {
722 while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
723 ++result;
724 }
725 DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
726 return result;
727 }
728
729 /*
730 * Find the button-index for a button named with --default-button. If the
731 * option was not specified, or if the selected button does not exist, return
732 * the index of the first button (usually "OK" unless --nook was used).
733 */
734 int
dlg_default_button(void)735 dlg_default_button(void)
736 {
737 int result = 0;
738
739 if (dialog_vars.default_button >= 0) {
740 int i, n;
741
742 for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
743 if (n == dialog_vars.default_button) {
744 result = i;
745 break;
746 }
747 }
748 }
749 DLG_TRACE(("# dlg_default_button() = %d\n", result));
750 return result;
751 }
752
753 /*
754 * Return a list of buttons for Yes/No labels.
755 */
756 const char **
dlg_yes_labels(void)757 dlg_yes_labels(void)
758 {
759 const char **result;
760
761 if (dialog_vars.extra_button) {
762 result = dlg_ok_labels();
763 } else {
764 static const char *labels[4];
765 int n = 0;
766
767 labels[n++] = my_yes_label();
768 labels[n++] = my_no_label();
769 if (dialog_vars.help_button)
770 labels[n++] = my_help_label();
771 labels[n] = 0;
772
773 result = labels;
774 }
775
776 return result;
777 }
778
779 /*
780 * Map the given button index for dlg_yes_labels() into our exit-code.
781 */
782 int
dlg_yes_buttoncode(int button)783 dlg_yes_buttoncode(int button)
784 {
785 int result = DLG_EXIT_ERROR;
786
787 if (dialog_vars.extra_button) {
788 result = dlg_ok_buttoncode(button);
789 } else if (button == 0) {
790 result = DLG_EXIT_OK;
791 } else if (button == 1) {
792 result = DLG_EXIT_CANCEL;
793 } else if (button == 2 && dialog_vars.help_button) {
794 result = DLG_EXIT_HELP;
795 }
796
797 return result;
798 }
799
800 /*
801 * Return the next index in labels[];
802 */
803 int
dlg_next_button(const char ** labels,int button)804 dlg_next_button(const char **labels, int button)
805 {
806 if (button < -1)
807 button = -1;
808
809 if (labels[button + 1] != 0) {
810 ++button;
811 } else {
812 button = MIN_BUTTON;
813 }
814 return button;
815 }
816
817 /*
818 * Return the previous index in labels[];
819 */
820 int
dlg_prev_button(const char ** labels,int button)821 dlg_prev_button(const char **labels, int button)
822 {
823 if (button > MIN_BUTTON) {
824 --button;
825 } else {
826 if (button < -1)
827 button = -1;
828
829 while (labels[button + 1] != 0)
830 ++button;
831 }
832 return button;
833 }
834