Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/dialog/buttons.c
39564 views
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
}
834
835