Merge branch 'vendor/DIALOG'
[dragonfly.git] / contrib / dialog / buttons.c
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 }