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