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