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