buttons.c revision 224014
1240116Smarcel/*
2240116Smarcel *  $Id: buttons.c,v 1.86 2011/06/28 10:46:46 tom Exp $
3240116Smarcel *
4240116Smarcel *  buttons.c -- draw buttons, e.g., OK/Cancel
5240116Smarcel *
6240116Smarcel *  Copyright 2000-2010,2011	Thomas E. Dickey
7240116Smarcel *
8240116Smarcel *  This program is free software; you can redistribute it and/or modify
9240116Smarcel *  it under the terms of the GNU Lesser General Public License, version 2.1
10240116Smarcel *  as published by the Free Software Foundation.
11240116Smarcel *
12240116Smarcel *  This program is distributed in the hope that it will be useful, but
13240116Smarcel *  WITHOUT ANY WARRANTY; without even the implied warranty of
14240116Smarcel *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15240116Smarcel *  Lesser General Public License for more details.
16240116Smarcel *
17240116Smarcel *  You should have received a copy of the GNU Lesser General Public
18240116Smarcel *  License along with this program; if not, write to
19240116Smarcel *	Free Software Foundation, Inc.
20240116Smarcel *	51 Franklin St., Fifth Floor
21240116Smarcel *	Boston, MA 02110, USA.
22240116Smarcel */
23240116Smarcel
24240116Smarcel#include <dialog.h>
25240116Smarcel#include <dlg_keys.h>
26273929Sjmmv
27273929Sjmmv#ifdef NEED_WCHAR_H
28240116Smarcel#include <wchar.h>
29240116Smarcel#endif
30240116Smarcel
31240116Smarcel#define MIN_BUTTON (dialog_state.visit_items ? -1 : 0)
32240116Smarcel
33240116Smarcelstatic void
34240116Smarcelcenter_label(char *buffer, int longest, const char *label)
35240116Smarcel{
36240116Smarcel    int len = dlg_count_columns(label);
37240116Smarcel    int left = 0, right = 0;
38240116Smarcel
39240116Smarcel    *buffer = 0;
40240116Smarcel    if (len < longest) {
41240116Smarcel	left = (longest - len) / 2;
42240116Smarcel	right = (longest - len - left);
43240116Smarcel	if (left > 0)
44273929Sjmmv	    sprintf(buffer, "%*s", left, " ");
45273929Sjmmv    }
46273929Sjmmv    strcat(buffer, label);
47273929Sjmmv    if (right > 0)
48273929Sjmmv	sprintf(buffer + strlen(buffer), "%*s", right, " ");
49240116Smarcel}
50240116Smarcel
51240116Smarcel/*
52240116Smarcel * Parse a multibyte character out of the string, set it past the parsed
53240116Smarcel * character.
54240116Smarcel */
55240116Smarcelstatic int
56240116Smarcelstring_to_char(const char **stringp)
57240116Smarcel{
58240116Smarcel    int result;
59251108Smarcel#ifdef USE_WIDE_CURSES
60240116Smarcel    const char *string = *stringp;
61240116Smarcel    size_t have = strlen(string);
62240116Smarcel    size_t check;
63240116Smarcel    size_t len;
64240116Smarcel    wchar_t cmp2[2];
65240116Smarcel    mbstate_t state;
66240116Smarcel
67240116Smarcel    memset(&state, 0, sizeof(state));
68240116Smarcel    len = mbrlen(string, have, &state);
69240116Smarcel    if ((int) len > 0 && len <= have) {
70240116Smarcel	memset(&state, 0, sizeof(state));
71240116Smarcel	memset(cmp2, 0, sizeof(cmp2));
72251108Smarcel	check = mbrtowc(cmp2, string, len, &state);
73240116Smarcel	if ((int) check <= 0)
74240116Smarcel	    cmp2[0] = 0;
75240116Smarcel	*stringp += len;
76240116Smarcel    } else {
77240116Smarcel	cmp2[0] = UCH(*string);
78240116Smarcel	*stringp += 1;
79240116Smarcel    }
80240116Smarcel    result = cmp2[0];
81240116Smarcel#else
82240116Smarcel    const char *string = *stringp;
83240116Smarcel    result = UCH(*string);
84240116Smarcel    *stringp += 1;
85240116Smarcel#endif
86240116Smarcel    return result;
87240116Smarcel}
88240116Smarcel
89240116Smarcel/*
90240116Smarcel * Print a button
91240116Smarcel */
92240116Smarcelstatic void
93240116Smarcelprint_button(WINDOW *win, char *label, int y, int x, int selected)
94240116Smarcel{
95240116Smarcel    int i;
96240116Smarcel    int state = 0;
97240116Smarcel    const int *indx = dlg_index_wchars(label);
98240116Smarcel    int limit = dlg_count_wchars(label);
99240116Smarcel    chtype key_attr = (selected
100240116Smarcel		       ? button_key_active_attr
101240116Smarcel		       : button_key_inactive_attr);
102240116Smarcel    chtype label_attr = (selected
103240116Smarcel			 ? button_label_active_attr
104240116Smarcel			 : button_label_inactive_attr);
105240116Smarcel
106240116Smarcel    (void) wmove(win, y, x);
107240116Smarcel    wattrset(win, selected
108240116Smarcel	     ? button_active_attr
109240116Smarcel	     : button_inactive_attr);
110240116Smarcel    (void) waddstr(win, "<");
111240116Smarcel    wattrset(win, label_attr);
112240116Smarcel    for (i = 0; i < limit; ++i) {
113240116Smarcel	int first = indx[i];
114240116Smarcel	int last = indx[i + 1];
115240116Smarcel
116240116Smarcel	switch (state) {
117240116Smarcel	case 0:
118240116Smarcel#ifdef USE_WIDE_CURSES
119240116Smarcel	    if ((last - first) != 1) {
120240116Smarcel		const char *temp = (label + first);
121240116Smarcel		int cmp = string_to_char(&temp);
122240116Smarcel		if (dlg_isupper(cmp)) {
123240116Smarcel		    wattrset(win, key_attr);
124240116Smarcel		    state = 1;
125240116Smarcel		}
126240116Smarcel		break;
127240116Smarcel	    }
128240116Smarcel#endif
129240116Smarcel	    if (dlg_isupper(UCH(label[first]))) {
130240116Smarcel		wattrset(win, key_attr);
131240116Smarcel		state = 1;
132240116Smarcel	    }
133240116Smarcel	    break;
134240116Smarcel	case 1:
135240116Smarcel	    wattrset(win, label_attr);
136240116Smarcel	    state = 2;
137240116Smarcel	    break;
138240116Smarcel	}
139240116Smarcel	waddnstr(win, label + first, last - first);
140240116Smarcel    }
141240116Smarcel    wattrset(win, selected
142240116Smarcel	     ? button_active_attr
143240116Smarcel	     : button_inactive_attr);
144240116Smarcel    (void) waddstr(win, ">");
145240116Smarcel    (void) wmove(win, y, x + ((int) strspn(label, " ")) + 1);
146240116Smarcel}
147240116Smarcel
148240116Smarcel/*
149240116Smarcel * Count the buttons in the list.
150240116Smarcel */
151240116Smarcelint
152240116Smarceldlg_button_count(const char **labels)
153240116Smarcel{
154240116Smarcel    int result = 0;
155240116Smarcel    while (*labels++ != 0)
156240116Smarcel	++result;
157240116Smarcel    return result;
158240116Smarcel}
159240116Smarcel
160240116Smarcel/*
161240116Smarcel * Compute the size of the button array in columns.  Return the total number of
162240116Smarcel * columns in *length, and the longest button's columns in *longest
163240116Smarcel */
164240116Smarcelvoid
165240116Smarceldlg_button_sizes(const char **labels,
166240116Smarcel		 int vertical,
167240116Smarcel		 int *longest,
168240116Smarcel		 int *length)
169240116Smarcel{
170240116Smarcel    int n;
171240116Smarcel
172240116Smarcel    *length = 0;
173240116Smarcel    *longest = 0;
174240116Smarcel    for (n = 0; labels[n] != 0; n++) {
175240116Smarcel	if (vertical) {
176240116Smarcel	    *length += 1;
177240116Smarcel	    *longest = 1;
178240116Smarcel	} else {
179240116Smarcel	    int len = dlg_count_columns(labels[n]);
180240116Smarcel	    if (len > *longest)
181240116Smarcel		*longest = len;
182240116Smarcel	    *length += len;
183240116Smarcel	}
184240116Smarcel    }
185240116Smarcel    /*
186240116Smarcel     * If we can, make all of the buttons the same size.  This is only optional
187240116Smarcel     * for buttons laid out horizontally.
188240116Smarcel     */
189240116Smarcel    if (*longest < 6 - (*longest & 1))
190240116Smarcel	*longest = 6 - (*longest & 1);
191258289Sjmmv    if (!vertical)
192258289Sjmmv	*length = *longest * n;
193240116Smarcel}
194240116Smarcel
195240116Smarcel/*
196258289Sjmmv * Compute the size of the button array.
197258289Sjmmv */
198258289Sjmmvint
199258289Sjmmvdlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
200240116Smarcel{
201240116Smarcel    int count = dlg_button_count(labels);
202240116Smarcel    int longest;
203240116Smarcel    int length;
204240116Smarcel    int unused;
205240116Smarcel    int used;
206240116Smarcel
207240116Smarcel    if (count == 0)
208240116Smarcel	return 0;
209240116Smarcel    dlg_button_sizes(labels, FALSE, &longest, &length);
210240116Smarcel    used = (length + (count * 2));
211258289Sjmmv    unused = limit - used;
212258289Sjmmv
213258289Sjmmv    if ((*gap = unused / (count + 3)) <= 0) {
214240116Smarcel	if ((*gap = unused / (count + 1)) <= 0)
215240116Smarcel	    *gap = 1;
216240116Smarcel	*margin = *gap;
217258289Sjmmv    } else {
218258289Sjmmv	*margin = *gap * 2;
219258289Sjmmv    }
220258289Sjmmv    *step = *gap + (used + count - 1) / count;
221240116Smarcel    return (*gap > 0) && (unused >= 0);
222240116Smarcel}
223240116Smarcel
224240116Smarcel/*
225240116Smarcel * Make sure there is enough space for the buttons
226240116Smarcel */
227240116Smarcelvoid
228240116Smarceldlg_button_layout(const char **labels, int *limit)
229240116Smarcel{
230240116Smarcel    int width = 1;
231240116Smarcel    int gap, margin, step;
232258289Sjmmv
233258289Sjmmv    if (labels != 0 && dlg_button_count(labels)) {
234240116Smarcel	while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
235240116Smarcel	    ++width;
236240116Smarcel	width += (4 * MARGIN);
237258289Sjmmv	if (width > COLS)
238258289Sjmmv	    width = COLS;
239258289Sjmmv	if (width > *limit)
240258289Sjmmv	    *limit = width;
241240116Smarcel    }
242240116Smarcel}
243240116Smarcel
244240116Smarcel/*
245240116Smarcel * Print a list of buttons at the given position.
246240116Smarcel */
247240116Smarcelvoid
248240116Smarceldlg_draw_buttons(WINDOW *win,
249240116Smarcel		 int y, int x,
250240116Smarcel		 const char **labels,
251240116Smarcel		 int selected,
252240116Smarcel		 int vertical,
253240116Smarcel		 int limit)
254240116Smarcel{
255240116Smarcel    chtype save = dlg_get_attrs(win);
256240116Smarcel    int n;
257240116Smarcel    int step = 0;
258240116Smarcel    int length;
259240116Smarcel    int longest;
260240116Smarcel    int final_x;
261240116Smarcel    int final_y;
262240116Smarcel    int gap;
263240116Smarcel    int margin;
264240116Smarcel    size_t need;
265240116Smarcel    char *buffer;
266240116Smarcel
267240116Smarcel    dlg_mouse_setbase(getbegx(win), getbegy(win));
268240116Smarcel
269240116Smarcel    getyx(win, final_y, final_x);
270240116Smarcel
271240116Smarcel    dlg_button_sizes(labels, vertical, &longest, &length);
272240116Smarcel
273240116Smarcel    if (vertical) {
274240116Smarcel	y += 1;
275240116Smarcel	step = 1;
276240116Smarcel    } else {
277240116Smarcel	dlg_button_x_step(labels, limit, &gap, &margin, &step);
278240116Smarcel	x += margin;
279240116Smarcel    }
280240116Smarcel
281240116Smarcel    /*
282240116Smarcel     * Allocate a buffer big enough for any label.
283240116Smarcel     */
284240116Smarcel    need = (size_t) longest;
285240116Smarcel    for (n = 0; labels[n] != 0; ++n) {
286240116Smarcel	need += strlen(labels[n]) + 1;
287240116Smarcel    }
288240116Smarcel    buffer = dlg_malloc(char, need);
289240116Smarcel    assert_ptr(buffer, "dlg_draw_buttons");
290240116Smarcel
291240116Smarcel    /*
292240116Smarcel     * Draw the labels.
293240116Smarcel     */
294240116Smarcel    for (n = 0; labels[n] != 0; n++) {
295240116Smarcel	center_label(buffer, longest, labels[n]);
296240116Smarcel	mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
297240116Smarcel	print_button(win, buffer, y, x,
298240116Smarcel		     (selected == n) || (n == 0 && selected < 0));
299240116Smarcel	if (selected == n)
300240116Smarcel	    getyx(win, final_y, final_x);
301240116Smarcel
302240116Smarcel	if (vertical) {
303240116Smarcel	    if ((y += step) > limit)
304240116Smarcel		break;
305240116Smarcel	} else {
306240116Smarcel	    if ((x += step) > limit)
307240116Smarcel		break;
308240116Smarcel	}
309240116Smarcel    }
310240116Smarcel    (void) wmove(win, final_y, final_x);
311240116Smarcel    wrefresh(win);
312240116Smarcel    free(buffer);
313240116Smarcel    wattrset(win, save);
314240116Smarcel}
315240116Smarcel
316240116Smarcel/*
317240116Smarcel * Match a given character against the beginning of the string, ignoring case
318240116Smarcel * of the given character.  The matching string must begin with an uppercase
319240116Smarcel * character.
320240116Smarcel */
321240116Smarcelint
322240116Smarceldlg_match_char(int ch, const char *string)
323240116Smarcel{
324240116Smarcel    if (string != 0) {
325240116Smarcel	int cmp2 = string_to_char(&string);
326240116Smarcel#ifdef USE_WIDE_CURSES
327240116Smarcel	wint_t cmp1 = dlg_toupper(ch);
328240116Smarcel	if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
329240116Smarcel	    return TRUE;
330240116Smarcel	}
331240116Smarcel#else
332240116Smarcel	if (ch > 0 && ch < 256) {
333240116Smarcel	    if (dlg_toupper(ch) == dlg_toupper(cmp2))
334240116Smarcel		return TRUE;
335240116Smarcel	}
336240116Smarcel#endif
337240116Smarcel    }
338240116Smarcel    return FALSE;
339240116Smarcel}
340240116Smarcel
341240116Smarcel/*
342240116Smarcel * Find the first uppercase character in the label, which we may use for an
343240116Smarcel * abbreviation.
344240116Smarcel */
345240116Smarcelint
346240116Smarceldlg_button_to_char(const char *label)
347240116Smarcel{
348240116Smarcel    int cmp = -1;
349240116Smarcel
350240116Smarcel    while (*label != 0) {
351240116Smarcel	cmp = string_to_char(&label);
352240116Smarcel	if (dlg_isupper(cmp)) {
353240116Smarcel	    break;
354240116Smarcel	}
355240116Smarcel    }
356240116Smarcel    return cmp;
357240116Smarcel}
358240116Smarcel
359240116Smarcel/*
360240116Smarcel * Given a list of button labels, and a character which may be the abbreviation
361240116Smarcel * for one, find it, if it exists.  An abbreviation will be the first character
362240116Smarcel * which happens to be capitalized in the label.
363240116Smarcel */
364240116Smarcelint
365240116Smarceldlg_char_to_button(int ch, const char **labels)
366240116Smarcel{
367240116Smarcel    if (labels != 0) {
368240116Smarcel	int j;
369240116Smarcel
370240116Smarcel	ch = (int) dlg_toupper(dlg_last_getc());
371240116Smarcel	for (j = 0; labels[j] != 0; ++j) {
372273929Sjmmv	    int cmp = dlg_button_to_char(labels[j]);
373240116Smarcel	    if (ch == cmp) {
374240116Smarcel		dlg_flush_getc();
375240116Smarcel		return j;
376240116Smarcel	    }
377240116Smarcel	}
378240116Smarcel    }
379240116Smarcel    return DLG_EXIT_UNKNOWN;
380240116Smarcel}
381240116Smarcel
382240116Smarcelstatic const char *
383240116Smarcelmy_yes_label(void)
384240116Smarcel{
385240116Smarcel    return (dialog_vars.yes_label != NULL)
386240116Smarcel	? dialog_vars.yes_label
387240116Smarcel	: _("Yes");
388240116Smarcel}
389240116Smarcel
390240116Smarcelstatic const char *
391240116Smarcelmy_no_label(void)
392240116Smarcel{
393240116Smarcel    return (dialog_vars.no_label != NULL)
394240116Smarcel	? dialog_vars.no_label
395	: _("No");
396}
397
398static const char *
399my_ok_label(void)
400{
401    return (dialog_vars.ok_label != NULL)
402	? dialog_vars.ok_label
403	: _("OK");
404}
405
406static const char *
407my_cancel_label(void)
408{
409    return (dialog_vars.cancel_label != NULL)
410	? dialog_vars.cancel_label
411	: _("Cancel");
412}
413
414static const char *
415my_exit_label(void)
416{
417    return (dialog_vars.exit_label != NULL)
418	? dialog_vars.exit_label
419	: _("EXIT");
420}
421
422static const char *
423my_extra_label(void)
424{
425    return (dialog_vars.extra_label != NULL)
426	? dialog_vars.extra_label
427	: _("Extra");
428}
429
430static const char *
431my_help_label(void)
432{
433    return (dialog_vars.help_label != NULL)
434	? dialog_vars.help_label
435	: _("Help");
436}
437
438/*
439 * Return a list of button labels.
440 */
441const char **
442dlg_exit_label(void)
443{
444    const char **result;
445    DIALOG_VARS save;
446
447    if (dialog_vars.extra_button) {
448	dlg_save_vars(&save);
449	dialog_vars.nocancel = TRUE;
450	result = dlg_ok_labels();
451	dlg_restore_vars(&save);
452    } else {
453	static const char *labels[3];
454	int n = 0;
455
456	if (!dialog_vars.nook)
457	    labels[n++] = my_exit_label();
458	if (dialog_vars.help_button)
459	    labels[n++] = my_help_label();
460	if (n == 0)
461	    labels[n++] = my_exit_label();
462	labels[n] = 0;
463
464	result = labels;
465    }
466    return result;
467}
468
469/*
470 * Map the given button index for dlg_exit_label() into our exit-code.
471 */
472int
473dlg_exit_buttoncode(int button)
474{
475    int result;
476    DIALOG_VARS save;
477
478    dlg_save_vars(&save);
479    dialog_vars.nocancel = TRUE;
480
481    result = dlg_ok_buttoncode(button);
482
483    dlg_restore_vars(&save);
484
485    return result;
486}
487
488const char **
489dlg_ok_label(void)
490{
491    static const char *labels[3];
492    int n = 0;
493
494    labels[n++] = my_ok_label();
495    if (dialog_vars.help_button)
496	labels[n++] = my_help_label();
497    labels[n] = 0;
498    return labels;
499}
500
501/*
502 * Return a list of button labels for the OK/Cancel group.
503 */
504const char **
505dlg_ok_labels(void)
506{
507    static const char *labels[5];
508    int n = 0;
509
510    if (!dialog_vars.nook)
511	labels[n++] = my_ok_label();
512    if (dialog_vars.extra_button)
513	labels[n++] = my_extra_label();
514    if (!dialog_vars.nocancel)
515	labels[n++] = my_cancel_label();
516    if (dialog_vars.help_button)
517	labels[n++] = my_help_label();
518    labels[n] = 0;
519    return labels;
520}
521
522/*
523 * Map the given button index for dlg_ok_labels() into our exit-code
524 */
525int
526dlg_ok_buttoncode(int button)
527{
528    int result = DLG_EXIT_ERROR;
529    int n = !dialog_vars.nook;
530
531    if (!dialog_vars.nook && (button <= 0)) {
532	result = DLG_EXIT_OK;
533    } else if (dialog_vars.extra_button && (button == n++)) {
534	result = DLG_EXIT_EXTRA;
535    } else if (!dialog_vars.nocancel && (button == n++)) {
536	result = DLG_EXIT_CANCEL;
537    } else if (dialog_vars.help_button && (button == n)) {
538	result = DLG_EXIT_HELP;
539    }
540    return result;
541}
542
543/*
544 * Given that we're using dlg_ok_labels() to list buttons, find the next index
545 * in the list of buttons.  The 'extra' parameter if negative provides a way to
546 * enumerate extra active areas on the widget.
547 */
548int
549dlg_next_ok_buttonindex(int current, int extra)
550{
551    int result = current + 1;
552
553    if (current >= 0
554	&& dlg_ok_buttoncode(result) < 0)
555	result = extra;
556    return result;
557}
558
559/*
560 * Similarly, find the previous button index.
561 */
562int
563dlg_prev_ok_buttonindex(int current, int extra)
564{
565    int result = current - 1;
566
567    if (result < extra) {
568	for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
569	    ;
570	}
571    }
572    return result;
573}
574
575/*
576 * Find the button-index for the "OK" or "Cancel" button, according to
577 * whether --defaultno is given.  If --nocancel was given, we always return
578 * the index for "OK".
579 */
580int
581dlg_defaultno_button(void)
582{
583    int result = 0;
584
585    if (dialog_vars.defaultno && !dialog_vars.nocancel) {
586	while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
587	    ++result;
588    }
589    return result;
590}
591
592/*
593 * Return a list of buttons for Yes/No labels.
594 */
595const char **
596dlg_yes_labels(void)
597{
598    const char **result;
599
600    if (dialog_vars.extra_button) {
601	result = dlg_ok_labels();
602    } else {
603	static const char *labels[4];
604	int n = 0;
605
606	labels[n++] = my_yes_label();
607	labels[n++] = my_no_label();
608	if (dialog_vars.help_button)
609	    labels[n++] = my_help_label();
610	labels[n] = 0;
611
612	result = labels;
613    }
614
615    return result;
616}
617
618/*
619 * Map the given button index for dlg_yes_labels() into our exit-code.
620 */
621int
622dlg_yes_buttoncode(int button)
623{
624    int result = DLG_EXIT_ERROR;
625
626    if (dialog_vars.extra_button) {
627	result = dlg_ok_buttoncode(button);
628    } else if (button == 0) {
629	result = DLG_EXIT_OK;
630    } else if (button == 1) {
631	result = DLG_EXIT_CANCEL;
632    } else if (button == 2 && dialog_vars.help_button) {
633	result = DLG_EXIT_HELP;
634    }
635
636    return result;
637}
638
639/*
640 * Return the next index in labels[];
641 */
642int
643dlg_next_button(const char **labels, int button)
644{
645    if (labels[button + 1] != 0)
646	++button;
647    else
648	button = MIN_BUTTON;
649    return button;
650}
651
652/*
653 * Return the previous index in labels[];
654 */
655int
656dlg_prev_button(const char **labels, int button)
657{
658    if (button > MIN_BUTTON)
659	--button;
660    else {
661	while (labels[button + 1] != 0)
662	    ++button;
663    }
664    return button;
665}
666