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