xref: /freebsd/contrib/dialog/buttons.c (revision 0b3105a37d7adcadcb720112fed4dc4e8040be99)
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 
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 static size_t
90 count_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  */
104 static int
105 was_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  */
129 static int *
130 get_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  */
166 static void
167 print_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  */
222 int
223 dlg_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  */
235 void
236 dlg_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  */
269 int
270 dlg_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  */
303 void
304 dlg_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  */
323 void
324 dlg_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  */
403 int
404 dlg_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  */
427 int
428 dlg_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  */
446 int
447 dlg_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 
472 static const char *
473 my_yes_label(void)
474 {
475     return (dialog_vars.yes_label != NULL)
476 	? dialog_vars.yes_label
477 	: _("Yes");
478 }
479 
480 static const char *
481 my_no_label(void)
482 {
483     return (dialog_vars.no_label != NULL)
484 	? dialog_vars.no_label
485 	: _("No");
486 }
487 
488 static const char *
489 my_ok_label(void)
490 {
491     return (dialog_vars.ok_label != NULL)
492 	? dialog_vars.ok_label
493 	: _("OK");
494 }
495 
496 static const char *
497 my_cancel_label(void)
498 {
499     return (dialog_vars.cancel_label != NULL)
500 	? dialog_vars.cancel_label
501 	: _("Cancel");
502 }
503 
504 static const char *
505 my_exit_label(void)
506 {
507     return (dialog_vars.exit_label != NULL)
508 	? dialog_vars.exit_label
509 	: _("EXIT");
510 }
511 
512 static const char *
513 my_extra_label(void)
514 {
515     return (dialog_vars.extra_label != NULL)
516 	? dialog_vars.extra_label
517 	: _("Extra");
518 }
519 
520 static const char *
521 my_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  */
531 const char **
532 dlg_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  */
562 int
563 dlg_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 
578 const char **
579 dlg_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  */
596 const char **
597 dlg_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  */
617 int
618 dlg_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  */
641 int
642 dlg_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  */
655 int
656 dlg_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  */
673 int
674 dlg_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  */
691 int
692 dlg_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  */
712 const char **
713 dlg_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  */
738 int
739 dlg_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  */
759 int
760 dlg_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  */
776 int
777 dlg_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