]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/dialog/buttons.c
less: upgrade to v581.2.
[FreeBSD/FreeBSD.git] / contrib / dialog / buttons.c
1 /*
2  *  $Id: buttons.c,v 1.106 2021/01/17 17:03:16 tom Exp $
3  *
4  *  buttons.c -- draw buttons, e.g., OK/Cancel
5  *
6  *  Copyright 2000-2020,2021    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     if (!dialog_vars.cursor_off_label) {
229         (void) wmove(win, y, x + ((int) (strspn) (label, " ")) + 1);
230     }
231 }
232
233 /*
234  * Count the buttons in the list.
235  */
236 int
237 dlg_button_count(const char **labels)
238 {
239     int result = 0;
240     while (*labels++ != 0)
241         ++result;
242     return result;
243 }
244
245 /*
246  * Compute the size of the button array in columns.  Return the total number of
247  * columns in *length, and the longest button's columns in *longest
248  */
249 void
250 dlg_button_sizes(const char **labels,
251                  int vertical,
252                  int *longest,
253                  int *length)
254 {
255     int n;
256
257     *length = 0;
258     *longest = 0;
259     for (n = 0; labels[n] != 0; n++) {
260         if (vertical) {
261             *length += 1;
262             *longest = 1;
263         } else {
264             int len = dlg_count_columns(labels[n]);
265             if (len > *longest)
266                 *longest = len;
267             *length += len;
268         }
269     }
270     /*
271      * If we can, make all of the buttons the same size.  This is only optional
272      * for buttons laid out horizontally.
273      */
274     if (*longest < 6 - (*longest & 1))
275         *longest = 6 - (*longest & 1);
276     if (!vertical)
277         *length = *longest * n;
278 }
279
280 /*
281  * Compute the size of the button array.
282  */
283 int
284 dlg_button_x_step(const char **labels, int limit, int *gap, int *margin, int *step)
285 {
286     int count = dlg_button_count(labels);
287     int longest;
288     int length;
289     int result;
290
291     *margin = 0;
292     if (count != 0) {
293         int unused;
294         int used;
295
296         dlg_button_sizes(labels, FALSE, &longest, &length);
297         used = (length + (count * 2));
298         unused = limit - used;
299
300         if ((*gap = unused / (count + 3)) <= 0) {
301             if ((*gap = unused / (count + 1)) <= 0)
302                 *gap = 1;
303             *margin = *gap;
304         } else {
305             *margin = *gap * 2;
306         }
307         *step = *gap + (used + count - 1) / count;
308         result = (*gap > 0) && (unused >= 0);
309     } else {
310         result = 0;
311     }
312     return result;
313 }
314
315 /*
316  * Make sure there is enough space for the buttons
317  */
318 void
319 dlg_button_layout(const char **labels, int *limit)
320 {
321     int gap, margin, step;
322
323     if (labels != 0 && dlg_button_count(labels)) {
324         int width = 1;
325
326         while (!dlg_button_x_step(labels, width, &gap, &margin, &step))
327             ++width;
328         width += (4 * MARGIN);
329         if (width > COLS)
330             width = COLS;
331         if (width > *limit)
332             *limit = width;
333     }
334 }
335
336 /*
337  * Print a list of buttons at the given position.
338  */
339 void
340 dlg_draw_buttons(WINDOW *win,
341                  int y, int x,
342                  const char **labels,
343                  int selected,
344                  int vertical,
345                  int limit)
346 {
347     chtype save = dlg_get_attrs(win);
348     int step = 0;
349     int length;
350     int longest;
351     int final_x;
352     int final_y;
353     int gap;
354     int margin;
355     size_t need;
356
357     dlg_mouse_setbase(getbegx(win), getbegy(win));
358
359     getyx(win, final_y, final_x);
360
361     dlg_button_sizes(labels, vertical, &longest, &length);
362
363     if (vertical) {
364         y += 1;
365         step = 1;
366     } else {
367         dlg_button_x_step(labels, limit, &gap, &margin, &step);
368         x += margin;
369     }
370
371     /*
372      * Allocate a buffer big enough for any label.
373      */
374     need = (size_t) longest;
375     if (need != 0) {
376         char *buffer;
377         int n;
378         int *hotkeys = get_hotkeys(labels);
379
380         assert_ptr(hotkeys, "dlg_draw_buttons");
381
382         for (n = 0; labels[n] != 0; ++n) {
383             need += strlen(labels[n]) + 1;
384         }
385         buffer = dlg_malloc(char, need);
386         assert_ptr(buffer, "dlg_draw_buttons");
387
388         /*
389          * Draw the labels.
390          */
391         for (n = 0; labels[n] != 0; n++) {
392             center_label(buffer, longest, labels[n]);
393             mouse_mkbutton(y, x, dlg_count_columns(buffer), n);
394             print_button(win, buffer,
395                          CHR_BUTTON ? hotkeys[n] : -1,
396                          y, x,
397                          (selected == n) || (n == 0 && selected < 0));
398             if (selected == n)
399                 getyx(win, final_y, final_x);
400
401             if (vertical) {
402                 if ((y += step) > limit)
403                     break;
404             } else {
405                 if ((x += step) > limit)
406                     break;
407             }
408         }
409         (void) wmove(win, final_y, final_x);
410         wrefresh(win);
411         dlg_attrset(win, save);
412         free(buffer);
413         free(hotkeys);
414     }
415 }
416
417 /*
418  * Match a given character against the beginning of the string, ignoring case
419  * of the given character.  The matching string must begin with an uppercase
420  * character.
421  */
422 int
423 dlg_match_char(int ch, const char *string)
424 {
425     if (!dialog_vars.no_hot_list) {
426         if (string != 0) {
427             int cmp2 = string_to_char(&string);
428 #ifdef USE_WIDE_CURSES
429             wint_t cmp1 = dlg_toupper(ch);
430             if (cmp2 != 0 && (wchar_t) cmp1 == (wchar_t) dlg_toupper(cmp2)) {
431                 return TRUE;
432             }
433 #else
434             if (ch > 0 && ch < 256) {
435                 if (dlg_toupper(ch) == dlg_toupper(cmp2))
436                     return TRUE;
437             }
438 #endif
439         }
440     }
441     return FALSE;
442 }
443
444 /*
445  * Find the first uppercase character in the label, which we may use for an
446  * abbreviation.
447  */
448 int
449 dlg_button_to_char(const char *label)
450 {
451     int cmp = -1;
452
453     while (*label != 0) {
454         int ch = string_to_char(&label);
455         if (dlg_isupper(ch)) {
456             cmp = ch;
457             break;
458         }
459     }
460     return cmp;
461 }
462
463 /*
464  * Given a list of button labels, and a character which may be the abbreviation
465  * for one, find it, if it exists.  An abbreviation will be the first character
466  * which happens to be capitalized in the label.
467  */
468 int
469 dlg_char_to_button(int ch, const char **labels)
470 {
471     int result = DLG_EXIT_UNKNOWN;
472
473     if (labels != 0) {
474         int *hotkeys = get_hotkeys(labels);
475
476         ch = (int) dlg_toupper(dlg_last_getc());
477
478         if (hotkeys != 0) {
479             int j;
480
481             for (j = 0; labels[j] != 0; ++j) {
482                 if (ch == hotkeys[j]) {
483                     dlg_flush_getc();
484                     result = j;
485                     break;
486                 }
487             }
488             free(hotkeys);
489         }
490     }
491
492     return result;
493 }
494
495 static const char *
496 my_yes_label(void)
497 {
498     return (dialog_vars.yes_label != NULL)
499         ? dialog_vars.yes_label
500         : _("Yes");
501 }
502
503 static const char *
504 my_no_label(void)
505 {
506     return (dialog_vars.no_label != NULL)
507         ? dialog_vars.no_label
508         : _("No");
509 }
510
511 static const char *
512 my_ok_label(void)
513 {
514     return (dialog_vars.ok_label != NULL)
515         ? dialog_vars.ok_label
516         : _("OK");
517 }
518
519 static const char *
520 my_cancel_label(void)
521 {
522     return (dialog_vars.cancel_label != NULL)
523         ? dialog_vars.cancel_label
524         : _("Cancel");
525 }
526
527 static const char *
528 my_exit_label(void)
529 {
530     return (dialog_vars.exit_label != NULL)
531         ? dialog_vars.exit_label
532         : _("EXIT");
533 }
534
535 static const char *
536 my_extra_label(void)
537 {
538     return (dialog_vars.extra_label != NULL)
539         ? dialog_vars.extra_label
540         : _("Extra");
541 }
542
543 static const char *
544 my_help_label(void)
545 {
546     return (dialog_vars.help_label != NULL)
547         ? dialog_vars.help_label
548         : _("Help");
549 }
550
551 /*
552  * Return a list of button labels.
553  */
554 const char **
555 dlg_exit_label(void)
556 {
557     const char **result;
558     DIALOG_VARS save;
559
560     if (dialog_vars.extra_button) {
561         dlg_save_vars(&save);
562         dialog_vars.nocancel = TRUE;
563         result = dlg_ok_labels();
564         dlg_restore_vars(&save);
565     } else {
566         static const char *labels[3];
567         int n = 0;
568
569         if (!dialog_vars.nook)
570             labels[n++] = my_exit_label();
571         if (dialog_vars.help_button)
572             labels[n++] = my_help_label();
573         if (n == 0)
574             labels[n++] = my_exit_label();
575         labels[n] = 0;
576
577         result = labels;
578     }
579     return result;
580 }
581
582 /*
583  * Map the given button index for dlg_exit_label() into our exit-code.
584  */
585 int
586 dlg_exit_buttoncode(int button)
587 {
588     int result;
589     DIALOG_VARS save;
590
591     dlg_save_vars(&save);
592     dialog_vars.nocancel = TRUE;
593
594     result = dlg_ok_buttoncode(button);
595
596     dlg_restore_vars(&save);
597
598     return result;
599 }
600
601 static const char **
602 finish_ok_label(const char **labels, int n)
603 {
604     if (n == 0) {
605         labels[n++] = my_ok_label();
606         dialog_vars.nook = FALSE;
607         dlg_trace_msg("# ignore --nook, since at least one button is needed\n");
608     }
609
610     labels[n] = NULL;
611     return labels;
612 }
613
614 /*
615  * Return a list of button labels for the OK (no Cancel) group, used in msgbox
616  * and progressbox.
617  */
618 const char **
619 dlg_ok_label(void)
620 {
621     static const char *labels[4];
622     int n = 0;
623
624     if (!dialog_vars.nook)
625         labels[n++] = my_ok_label();
626     if (dialog_vars.extra_button)
627         labels[n++] = my_extra_label();
628     if (dialog_vars.help_button)
629         labels[n++] = my_help_label();
630
631     return finish_ok_label(labels, n);
632 }
633
634 /*
635  * Return a list of button labels for the OK/Cancel group, used in most widgets
636  * that select an option or data.
637  */
638 const char **
639 dlg_ok_labels(void)
640 {
641     static const char *labels[5];
642     int n = 0;
643
644     if (!dialog_vars.nook)
645         labels[n++] = my_ok_label();
646     if (dialog_vars.extra_button)
647         labels[n++] = my_extra_label();
648     if (!dialog_vars.nocancel)
649         labels[n++] = my_cancel_label();
650     if (dialog_vars.help_button)
651         labels[n++] = my_help_label();
652
653     return finish_ok_label(labels, n);
654 }
655
656 /*
657  * Map the given button index for dlg_ok_labels() into our exit-code
658  */
659 int
660 dlg_ok_buttoncode(int button)
661 {
662     int result = DLG_EXIT_ERROR;
663     int n = !dialog_vars.nook;
664
665     if (!dialog_vars.nook && (button <= 0)) {
666         result = DLG_EXIT_OK;
667     } else if (dialog_vars.extra_button && (button == n++)) {
668         result = DLG_EXIT_EXTRA;
669     } else if (!dialog_vars.nocancel && (button == n++)) {
670         result = DLG_EXIT_CANCEL;
671     } else if (dialog_vars.help_button && (button == n)) {
672         result = DLG_EXIT_HELP;
673     }
674     DLG_TRACE(("# dlg_ok_buttoncode(%d) = %d:%s\n",
675                button, result, dlg_exitcode2s(result)));
676     return result;
677 }
678
679 /*
680  * Given that we're using dlg_ok_labels() to list buttons, find the next index
681  * in the list of buttons.  The 'extra' parameter if negative provides a way to
682  * enumerate extra active areas on the widget.
683  */
684 int
685 dlg_next_ok_buttonindex(int current, int extra)
686 {
687     int result = current + 1;
688
689     if (current >= 0
690         && dlg_ok_buttoncode(result) < 0)
691         result = extra;
692     return result;
693 }
694
695 /*
696  * Similarly, find the previous button index.
697  */
698 int
699 dlg_prev_ok_buttonindex(int current, int extra)
700 {
701     int result = current - 1;
702
703     if (result < extra) {
704         for (result = 0; dlg_ok_buttoncode(result + 1) >= 0; ++result) {
705             ;
706         }
707     }
708     return result;
709 }
710
711 /*
712  * Find the button-index for the "OK" or "Cancel" button, according to
713  * whether --defaultno is given.  If --nocancel was given, we always return
714  * the index for the first button (usually "OK" unless --nook was used).
715  */
716 int
717 dlg_defaultno_button(void)
718 {
719     int result = 0;
720
721     if (dialog_vars.defaultno && !dialog_vars.nocancel) {
722         while (dlg_ok_buttoncode(result) != DLG_EXIT_CANCEL)
723             ++result;
724     }
725     DLG_TRACE(("# dlg_defaultno_button() = %d\n", result));
726     return result;
727 }
728
729 /*
730  * Find the button-index for a button named with --default-button. If the
731  * option was not specified, or if the selected button does not exist, return
732  * the index of the first button (usually "OK" unless --nook was used).
733  */
734 int
735 dlg_default_button(void)
736 {
737     int result = 0;
738
739     if (dialog_vars.default_button >= 0) {
740         int i, n;
741
742         for (i = 0; (n = dlg_ok_buttoncode(i)) >= 0; i++) {
743             if (n == dialog_vars.default_button) {
744                 result = i;
745                 break;
746             }
747         }
748     }
749     DLG_TRACE(("# dlg_default_button() = %d\n", result));
750     return result;
751 }
752
753 /*
754  * Return a list of buttons for Yes/No labels.
755  */
756 const char **
757 dlg_yes_labels(void)
758 {
759     const char **result;
760
761     if (dialog_vars.extra_button) {
762         result = dlg_ok_labels();
763     } else {
764         static const char *labels[4];
765         int n = 0;
766
767         labels[n++] = my_yes_label();
768         labels[n++] = my_no_label();
769         if (dialog_vars.help_button)
770             labels[n++] = my_help_label();
771         labels[n] = 0;
772
773         result = labels;
774     }
775
776     return result;
777 }
778
779 /*
780  * Map the given button index for dlg_yes_labels() into our exit-code.
781  */
782 int
783 dlg_yes_buttoncode(int button)
784 {
785     int result = DLG_EXIT_ERROR;
786
787     if (dialog_vars.extra_button) {
788         result = dlg_ok_buttoncode(button);
789     } else if (button == 0) {
790         result = DLG_EXIT_OK;
791     } else if (button == 1) {
792         result = DLG_EXIT_CANCEL;
793     } else if (button == 2 && dialog_vars.help_button) {
794         result = DLG_EXIT_HELP;
795     }
796
797     return result;
798 }
799
800 /*
801  * Return the next index in labels[];
802  */
803 int
804 dlg_next_button(const char **labels, int button)
805 {
806     if (button < -1)
807         button = -1;
808
809     if (labels[button + 1] != 0) {
810         ++button;
811     } else {
812         button = MIN_BUTTON;
813     }
814     return button;
815 }
816
817 /*
818  * Return the previous index in labels[];
819  */
820 int
821 dlg_prev_button(const char **labels, int button)
822 {
823     if (button > MIN_BUTTON) {
824         --button;
825     } else {
826         if (button < -1)
827             button = -1;
828
829         while (labels[button + 1] != 0)
830             ++button;
831     }
832     return button;
833 }