buttons.c revision 220749
1/*
2 *  $Id: buttons.c,v 1.84 2011/01/19 00:27:53 tom Exp $
3 *
4 *  buttons.c -- draw buttons, e.g., OK/Cancel
5 *
6 *  Copyright 2000-2010,2011	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_items ? -1 : 0)
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
89/*
90 * Print a button
91 */
92static void
93print_button(WINDOW *win, char *label, int y, int x, int selected)
94{
95    int i;
96    int state = 0;
97    const int *indx = dlg_index_wchars(label);
98    int limit = dlg_count_wchars(label);
99    chtype key_attr = (selected
100		       ? button_key_active_attr
101		       : button_key_inactive_attr);
102    chtype label_attr = (selected
103			 ? button_label_active_attr
104			 : button_label_inactive_attr);
105
106    (void) wmove(win, y, x);
107    wattrset(win, selected
108	     ? button_active_attr
109	     : button_inactive_attr);
110    (void) waddstr(win, "<");
111    wattrset(win, label_attr);
112    for (i = 0; i < limit; ++i) {
113	int first = indx[i];
114	int last = indx[i + 1];
115
116	switch (state) {
117	case 0:
118#ifdef USE_WIDE_CURSES
119	    if ((last - first) != 1) {
120		const char *temp = (label + first);
121		int cmp = string_to_char(&temp);
122		if (dlg_isupper(cmp)) {
123		    wattrset(win, key_attr);
124		    state = 1;
125		}
126		break;
127	    }
128#endif
129	    if (dlg_isupper(UCH(label[first]))) {
130		wattrset(win, key_attr);
131		state = 1;
132	    }
133	    break;
134	case 1:
135	    wattrset(win, label_attr);
136	    state = 2;
137	    break;
138	}
139	waddnstr(win, label + first, last - first);
140    }
141    wattrset(win, selected
142	     ? button_active_attr
143	     : button_inactive_attr);
144    (void) waddstr(win, ">");
145    (void) wmove(win, y, x + ((int) strspn(label, " ")) + 1);
146}
147
148/*
149 * Count the buttons in the list.
150 */
151int
152dlg_button_count(const char **labels)
153{
154    int result = 0;
155    while (*labels++ != 0)
156	++result;
157    return result;
158}
159
160/*
161 * Compute the size of the button array in columns.  Return the total number of
162 * columns in *length, and the longest button's columns in *longest
163 */
164void
165dlg_button_sizes(const char **labels,
166		 int vertical,
167		 int *longest,
168		 int *length)
169{
170    int n;
171
172    *length = 0;
173    *longest = 0;
174    for (n = 0; labels[n] != 0; n++) {
175	if (vertical) {
176	    *length += 1;
177	    *longest = 1;
178	} else {
179	    int len = dlg_count_columns(labels[n]);
180	    if (len > *longest)
181		*longest = len;
182	    *length += len;
183	}
184    }
185    /*
186     * If we can, make all of the buttons the same size.  This is only optional
187     * for buttons laid out horizontally.
188     */
189    if (*longest < 6 - (*longest & 1))
190	*longest = 6 - (*longest & 1);
191    if (!vertical)
192	*length = *longest * n;
193}
194
195/*
196 * Compute the size of the button array.
197 */
198int
199dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
200{
201    int count = dlg_button_count(labels);
202    int longest;
203    int length;
204    int unused;
205    int used;
206
207    if (count == 0)
208	return 0;
209    dlg_button_sizes(labels, FALSE, &longest, &length);
210    used = (length + (count * 2));
211    unused = limit - used;
212
213    if ((*gap = unused / (count + 3)) <= 0) {
214	if ((*gap = unused / (count + 1)) <= 0)
215	    *gap = 1;
216	*margin = *gap;
217    } else {
218	*margin = *gap * 2;
219    }
220    *step = *gap + (used + count - 1) / count;
221    return (*gap > 0) && (unused >= 0);
222}
223
224/*
225 * Make sure there is enough space for the buttons
226 */
227void
228dlg_button_layout(const char **labels, int *limit)
229{
230    int width = 1;
231    int gap, margin, step;
232
233    if (labels != 0 && dlg_button_count(labels)) {
234	while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
235	    ++width;
236	width += (4 * MARGIN);
237	if (width > COLS)
238	    width = COLS;
239	if (width > *limit)
240	    *limit = width;
241    }
242}
243
244/*
245 * Print a list of buttons at the given position.
246 */
247void
248dlg_draw_buttons(WINDOW *win,
249		 int y, int x,
250		 const char **labels,
251		 int selected,
252		 int vertical,
253		 int limit)
254{
255    chtype save = dlg_get_attrs(win);
256    int n;
257    int step = 0;
258    int length;
259    int longest;
260    int final_x;
261    int final_y;
262    int gap;
263    int margin;
264    size_t need;
265    char *buffer;
266
267    dlg_mouse_setbase(getbegx(win), getbegy(win));
268
269    getyx(win, final_y, final_x);
270
271    dlg_button_sizes(labels, vertical, &longest, &length);
272
273    if (vertical) {
274	y += 1;
275	step = 1;
276    } else {
277	dlg_button_x_step(labels, limit, &gap, &margin, &step);
278	x += margin;
279    }
280
281    /*
282     * Allocate a buffer big enough for any label.
283     */
284    need = (size_t) longest;
285    for (n = 0; labels[n] != 0; ++n) {
286	need += strlen(labels[n]) + 1;
287    }
288    buffer = dlg_malloc(char, need);
289    assert_ptr(buffer, "dlg_draw_buttons");
290
291    /*
292     * Draw the labels.
293     */
294    for (n = 0; labels[n] != 0; n++) {
295	center_label(buffer, longest, labels[n]);
296	mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
297	print_button(win, buffer, y, x,
298		     (selected == n) || (n == 0 && selected < 0));
299	if (selected == n)
300	    getyx(win, final_y, final_x);
301
302	if (vertical) {
303	    if ((y += step) > limit)
304		break;
305	} else {
306	    if ((x += step) > limit)
307		break;
308	}
309    }
310    (void) wmove(win, final_y, final_x);
311    wrefresh(win);
312    free(buffer);
313    wattrset(win, save);
314}
315
316/*
317 * Match a given character against the beginning of the string, ignoring case
318 * of the given character.  The matching string must begin with an uppercase
319 * character.
320 */
321int
322dlg_match_char(int ch, const char *string)
323{
324    if (string != 0) {
325	int cmp2 = string_to_char(&string);
326#ifdef USE_WIDE_CURSES
327	wint_t cmp1 = dlg_toupper(ch);
328	if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
329	    return TRUE;
330	}
331#else
332	if (ch > 0 && ch < 256) {
333	    if (dlg_toupper(ch) == dlg_toupper(cmp2))
334		return TRUE;
335	}
336#endif
337    }
338    return FALSE;
339}
340
341/*
342 * Find the first uppercase character in the label, which we may use for an
343 * abbreviation.
344 */
345int
346dlg_button_to_char(const char *label)
347{
348    int cmp = -1;
349
350    while (*label != 0) {
351	cmp = string_to_char(&label);
352	if (dlg_isupper(cmp)) {
353	    break;
354	}
355    }
356    return cmp;
357}
358
359/*
360 * Given a list of button labels, and a character which may be the abbreviation
361 * for one, find it, if it exists.  An abbreviation will be the first character
362 * which happens to be capitalized in the label.
363 */
364int
365dlg_char_to_button(int ch, const char **labels)
366{
367    if (labels != 0) {
368	int j;
369
370	ch = (int) dlg_toupper(dlg_last_getc());
371	for (j = 0; labels[j] != 0; ++j) {
372	    int cmp = dlg_button_to_char(labels[j]);
373	    if (ch == cmp) {
374		dlg_flush_getc();
375		return j;
376	    }
377	}
378    }
379    return DLG_EXIT_UNKNOWN;
380}
381
382static const char *
383my_yes_label(void)
384{
385    return (dialog_vars.yes_label != NULL)
386	? dialog_vars.yes_label
387	: _("Yes");
388}
389
390static const char *
391my_no_label(void)
392{
393    return (dialog_vars.no_label != NULL)
394	? 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
446    if (dialog_vars.extra_button) {
447	result = dlg_ok_labels();
448    } else {
449	static const char *labels[3];
450	int n = 0;
451
452	labels[n++] = my_exit_label();
453	if (dialog_vars.help_button)
454	    labels[n++] = my_help_label();
455	labels[n] = 0;
456
457	result = labels;
458    }
459    return result;
460}
461
462/*
463 * Map the given button index for dlg_exit_label() into our exit-code.
464 */
465int
466dlg_exit_buttoncode(int button)
467{
468    return dlg_ok_buttoncode(button);
469}
470
471const char **
472dlg_ok_label(void)
473{
474    static const char *labels[3];
475    int n = 0;
476
477    labels[n++] = my_ok_label();
478    if (dialog_vars.help_button)
479	labels[n++] = my_help_label();
480    labels[n] = 0;
481    return labels;
482}
483
484/*
485 * Return a list of button labels for the OK/Cancel group.
486 */
487const char **
488dlg_ok_labels(void)
489{
490    static const char *labels[5];
491    int n = 0;
492
493    if (!dialog_vars.nook)
494	labels[n++] = my_ok_label();
495    if (dialog_vars.extra_button)
496	labels[n++] = my_extra_label();
497    if (!dialog_vars.nocancel)
498	labels[n++] = my_cancel_label();
499    if (dialog_vars.help_button)
500	labels[n++] = my_help_label();
501    labels[n] = 0;
502    return labels;
503}
504
505/*
506 * Map the given button index for dlg_ok_labels() into our exit-code
507 */
508int
509dlg_ok_buttoncode(int button)
510{
511    int result = DLG_EXIT_ERROR;
512    int n = !dialog_vars.nook;
513
514    if (!dialog_vars.nook && (button <= 0)) {
515	result = DLG_EXIT_OK;
516    } else if (dialog_vars.extra_button && (button == n++)) {
517	result = DLG_EXIT_EXTRA;
518    } else if (!dialog_vars.nocancel && (button == n++)) {
519	result = DLG_EXIT_CANCEL;
520    } else if (dialog_vars.help_button && (button == n)) {
521	result = DLG_EXIT_HELP;
522    }
523    return result;
524}
525
526/*
527 * Given that we're using dlg_ok_labels() to list buttons, find the next index
528 * in the list of buttons.  The 'extra' parameter if negative provides a way to
529 * enumerate extra active areas on the widget.
530 */
531int
532dlg_next_ok_buttonindex(int current, int extra)
533{
534    int result = current + 1;
535
536    if (current >= 0
537	&& dlg_ok_buttoncode(result) < 0)
538	result = extra;
539    return result;
540}
541
542/*
543 * Similarly, find the previous button index.
544 */
545int
546dlg_prev_ok_buttonindex(int current, int extra)
547{
548    int result = current - 1;
549
550    if (result < extra) {
551	for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
552	    ;
553	}
554    }
555    return result;
556}
557
558/*
559 * Find the button-index for the "OK" or "Cancel" button, according to
560 * whether --defaultno is given.  If --nocancel was given, we always return
561 * the index for "OK".
562 */
563int
564dlg_defaultno_button(void)
565{
566    int result = 0;
567
568    if (dialog_vars.defaultno && !dialog_vars.nocancel) {
569	while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
570	    ++result;
571    }
572    return result;
573}
574
575/*
576 * Return a list of buttons for Yes/No labels.
577 */
578const char **
579dlg_yes_labels(void)
580{
581    const char **result;
582
583    if (dialog_vars.extra_button) {
584	result = dlg_ok_labels();
585    } else {
586	static const char *labels[4];
587	int n = 0;
588
589	labels[n++] = my_yes_label();
590	labels[n++] = my_no_label();
591	if (dialog_vars.help_button)
592	    labels[n++] = my_help_label();
593	labels[n] = 0;
594
595	result = labels;
596    }
597
598    return result;
599}
600
601/*
602 * Map the given button index for dlg_yes_labels() into our exit-code.
603 */
604int
605dlg_yes_buttoncode(int button)
606{
607    int result = DLG_EXIT_ERROR;
608
609    if (dialog_vars.extra_button) {
610	result = dlg_ok_buttoncode(button);
611    } else if (button == 0) {
612	result = DLG_EXIT_OK;
613    } else if (button == 1) {
614	result = DLG_EXIT_CANCEL;
615    } else if (button == 2 && dialog_vars.help_button) {
616	result = DLG_EXIT_HELP;
617    }
618
619    return result;
620}
621
622/*
623 * Return the next index in labels[];
624 */
625int
626dlg_next_button(const char **labels, int button)
627{
628    if (labels[button + 1] != 0)
629	++button;
630    else
631	button = MIN_BUTTON;
632    return button;
633}
634
635/*
636 * Return the previous index in labels[];
637 */
638int
639dlg_prev_button(const char **labels, int button)
640{
641    if (button > MIN_BUTTON)
642	--button;
643    else {
644	while (labels[button + 1] != 0)
645	    ++button;
646    }
647    return button;
648}
649