xref: /freebsd/contrib/dialog/buttons.c (revision 300675f6fc464ea902932e6ee4aa2ac98e3f3c34)
1 /*
2  *  $Id: buttons.c,v 1.90 2012/07/01 20:42:05 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_items ? -1 : 0)
32 
33 static void
34 center_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  */
55 static int
56 string_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  */
92 static void
93 print_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     (void) wattrset(win, selected
108 		    ? button_active_attr
109 		    : button_inactive_attr);
110     (void) waddstr(win, "<");
111     (void) 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 		    (void) wattrset(win, key_attr);
124 		    state = 1;
125 		}
126 		break;
127 	    }
128 #endif
129 	    if (dlg_isupper(UCH(label[first]))) {
130 		(void) 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     (void) 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  */
151 int
152 dlg_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  */
164 void
165 dlg_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  */
198 int
199 dlg_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  */
227 void
228 dlg_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  */
247 void
248 dlg_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     (void) 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  */
321 int
322 dlg_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  */
345 int
346 dlg_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  */
364 int
365 dlg_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 
382 static const char *
383 my_yes_label(void)
384 {
385     return (dialog_vars.yes_label != NULL)
386 	? dialog_vars.yes_label
387 	: _("Yes");
388 }
389 
390 static const char *
391 my_no_label(void)
392 {
393     return (dialog_vars.no_label != NULL)
394 	? dialog_vars.no_label
395 	: _("No");
396 }
397 
398 static const char *
399 my_ok_label(void)
400 {
401     return (dialog_vars.ok_label != NULL)
402 	? dialog_vars.ok_label
403 	: _("OK");
404 }
405 
406 static const char *
407 my_cancel_label(void)
408 {
409     return (dialog_vars.cancel_label != NULL)
410 	? dialog_vars.cancel_label
411 	: _("Cancel");
412 }
413 
414 static const char *
415 my_exit_label(void)
416 {
417     return (dialog_vars.exit_label != NULL)
418 	? dialog_vars.exit_label
419 	: _("EXIT");
420 }
421 
422 static const char *
423 my_extra_label(void)
424 {
425     return (dialog_vars.extra_label != NULL)
426 	? dialog_vars.extra_label
427 	: _("Extra");
428 }
429 
430 static const char *
431 my_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  */
441 const char **
442 dlg_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  */
472 int
473 dlg_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 
488 const char **
489 dlg_ok_label(void)
490 {
491     static const char *labels[4];
492     int n = 0;
493 
494     labels[n++] = my_ok_label();
495     if (dialog_vars.extra_button)
496 	labels[n++] = my_extra_label();
497     if (dialog_vars.help_button)
498 	labels[n++] = my_help_label();
499     labels[n] = 0;
500     return labels;
501 }
502 
503 /*
504  * Return a list of button labels for the OK/Cancel group.
505  */
506 const char **
507 dlg_ok_labels(void)
508 {
509     static const char *labels[5];
510     int n = 0;
511 
512     if (!dialog_vars.nook)
513 	labels[n++] = my_ok_label();
514     if (dialog_vars.extra_button)
515 	labels[n++] = my_extra_label();
516     if (!dialog_vars.nocancel)
517 	labels[n++] = my_cancel_label();
518     if (dialog_vars.help_button)
519 	labels[n++] = my_help_label();
520     labels[n] = 0;
521     return labels;
522 }
523 
524 /*
525  * Map the given button index for dlg_ok_labels() into our exit-code
526  */
527 int
528 dlg_ok_buttoncode(int button)
529 {
530     int result = DLG_EXIT_ERROR;
531     int n = !dialog_vars.nook;
532 
533     if (!dialog_vars.nook && (button <= 0)) {
534 	result = DLG_EXIT_OK;
535     } else if (dialog_vars.extra_button && (button == n++)) {
536 	result = DLG_EXIT_EXTRA;
537     } else if (!dialog_vars.nocancel && (button == n++)) {
538 	result = DLG_EXIT_CANCEL;
539     } else if (dialog_vars.help_button && (button == n)) {
540 	result = DLG_EXIT_HELP;
541     }
542     dlg_trace_msg("# dlg_ok_buttoncode(%d) = %d\n", button, result);
543     return result;
544 }
545 
546 /*
547  * Given that we're using dlg_ok_labels() to list buttons, find the next index
548  * in the list of buttons.  The 'extra' parameter if negative provides a way to
549  * enumerate extra active areas on the widget.
550  */
551 int
552 dlg_next_ok_buttonindex(int current, int extra)
553 {
554     int result = current + 1;
555 
556     if (current >= 0
557 	&& dlg_ok_buttoncode(result) < 0)
558 	result = extra;
559     return result;
560 }
561 
562 /*
563  * Similarly, find the previous button index.
564  */
565 int
566 dlg_prev_ok_buttonindex(int current, int extra)
567 {
568     int result = current - 1;
569 
570     if (result < extra) {
571 	for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
572 	    ;
573 	}
574     }
575     return result;
576 }
577 
578 /*
579  * Find the button-index for the "OK" or "Cancel" button, according to
580  * whether --defaultno is given.  If --nocancel was given, we always return
581  * the index for the first button (usually "OK" unless --nook was used).
582  */
583 int
584 dlg_defaultno_button(void)
585 {
586     int result = 0;
587 
588     if (dialog_vars.defaultno && !dialog_vars.nocancel) {
589 	while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
590 	    ++result;
591     }
592     dlg_trace_msg("# dlg_defaultno_button() = %d\n", result);
593     return result;
594 }
595 
596 /*
597  * Find the button-index for a button named with --default-button. If the
598  * option was not specified, or if the selected button does not exist, return
599  * the index of the first button (usually "OK" unless --nook was used).
600  */
601 int
602 dlg_default_button(void)
603 {
604     int i, n;
605     int result = 0;
606 
607     if (dialog_vars.default_button >= 0) {
608 	for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
609 	    if (n == dialog_vars.default_button) {
610 		result = i;
611 		break;
612 	    }
613 	}
614     }
615     dlg_trace_msg("# dlg_default_button() = %d\n", result);
616     return result;
617 }
618 
619 /*
620  * Return a list of buttons for Yes/No labels.
621  */
622 const char **
623 dlg_yes_labels(void)
624 {
625     const char **result;
626 
627     if (dialog_vars.extra_button) {
628 	result = dlg_ok_labels();
629     } else {
630 	static const char *labels[4];
631 	int n = 0;
632 
633 	labels[n++] = my_yes_label();
634 	labels[n++] = my_no_label();
635 	if (dialog_vars.help_button)
636 	    labels[n++] = my_help_label();
637 	labels[n] = 0;
638 
639 	result = labels;
640     }
641 
642     return result;
643 }
644 
645 /*
646  * Map the given button index for dlg_yes_labels() into our exit-code.
647  */
648 int
649 dlg_yes_buttoncode(int button)
650 {
651     int result = DLG_EXIT_ERROR;
652 
653     if (dialog_vars.extra_button) {
654 	result = dlg_ok_buttoncode(button);
655     } else if (button == 0) {
656 	result = DLG_EXIT_OK;
657     } else if (button == 1) {
658 	result = DLG_EXIT_CANCEL;
659     } else if (button == 2 && dialog_vars.help_button) {
660 	result = DLG_EXIT_HELP;
661     }
662 
663     return result;
664 }
665 
666 /*
667  * Return the next index in labels[];
668  */
669 int
670 dlg_next_button(const char **labels, int button)
671 {
672     if (labels[button + 1] != 0)
673 	++button;
674     else
675 	button = MIN_BUTTON;
676     return button;
677 }
678 
679 /*
680  * Return the previous index in labels[];
681  */
682 int
683 dlg_prev_button(const char **labels, int button)
684 {
685     if (button > MIN_BUTTON)
686 	--button;
687     else {
688 	while (labels[button + 1] != 0)
689 	    ++button;
690     }
691     return button;
692 }
693