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
center_label(char * buffer,int longest,const char * label)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
string_to_char(const char ** stringp)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
count_labels(const char ** labels)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
was_hotkey(int this_key,int * used_keys,size_t next)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 *
get_hotkeys(const char ** labels)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
print_button(WINDOW * win,char * label,int hotkey,int y,int x,int selected)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
dlg_button_count(const char ** labels)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
dlg_button_sizes(const char ** labels,int vertical,int * longest,int * length)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
dlg_button_x_step(const char ** labels,int limit,int * gap,int * margin,int * step)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
dlg_button_layout(const char ** labels,int * limit)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
dlg_draw_buttons(WINDOW * win,int y,int x,const char ** labels,int selected,int vertical,int limit)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
dlg_match_char(int ch,const char * string)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
dlg_button_to_char(const char * label)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
dlg_char_to_button(int ch,const char ** labels)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 *
my_yes_label(void)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 *
my_no_label(void)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 *
my_ok_label(void)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 *
my_cancel_label(void)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 *
my_exit_label(void)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 *
my_extra_label(void)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 *
my_help_label(void)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 **
dlg_exit_label(void)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
dlg_exit_buttoncode(int button)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 **
dlg_ok_label(void)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 **
dlg_ok_labels(void)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
dlg_ok_buttoncode(int button)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
dlg_next_ok_buttonindex(int current,int extra)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
dlg_prev_ok_buttonindex(int current,int extra)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
dlg_defaultno_button(void)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
dlg_default_button(void)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 **
dlg_yes_labels(void)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
dlg_yes_buttoncode(int button)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
dlg_next_button(const char ** labels,int button)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
dlg_prev_button(const char ** labels,int button)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