xref: /freebsd/contrib/dialog/buttons.c (revision 406a584d7e80c2617dc035ede0d922215a12141c)
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
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
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
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
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 *
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
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
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
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
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
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
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
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
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 **
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
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 **
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 **
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
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
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
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
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
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 **
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
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
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
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