]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bsddialog/lib/menubox.c
ssh: Update to OpenSSH 9.7p1
[FreeBSD/FreeBSD.git] / contrib / bsddialog / lib / menubox.c
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2021-2023 Alfonso Sabato Siciliano
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <curses.h>
29 #include <stdlib.h>
30
31 #include "bsddialog.h"
32 #include "bsddialog_theme.h"
33 #include "lib_util.h"
34
35 enum menumode {
36         CHECKLISTMODE,
37         MENUMODE,
38         MIXEDLISTMODE,
39         RADIOLISTMODE,
40         SEPARATORMODE
41 };
42
43 struct privateitem {
44         const char *prefix;
45         bool on;               /* menu changes, not API on */
46         unsigned int depth;
47         const char *name;
48         const char *desc;
49         const char *bottomdesc;
50         int group;             /* index menu in menugroup */
51         int index;             /* real item index inside its menu */
52         enum menumode type;
53         wchar_t shortcut;
54 };
55
56 struct privatemenu {
57         WINDOW *box;              /* only for borders */
58         WINDOW *pad;              /* pad for the private items */
59         int ypad;                 /* start pad line */
60         int ys, ye, xs, xe;       /* pad pos */
61         unsigned int xselector;   /* [] */
62         unsigned int xname;       /* real x: xname + item.depth */
63         unsigned int xdesc;       /* real x: xdesc + item.depth */
64         unsigned int line;        /* wpad: prefix [] depth name desc */
65         unsigned int apimenurows;
66         unsigned int menurows;    /* real menurows after menu_size_position() */
67         int nitems;               /* total nitems (all groups * all items) */
68         struct privateitem *pritems;
69         int sel;                  /* current focus item, can be -1 */
70         bool hasbottomdesc;
71 };
72
73 static enum menumode
74 getmode(enum menumode mode, struct bsddialog_menugroup group)
75 {
76         if (mode == MIXEDLISTMODE) {
77                 if (group.type == BSDDIALOG_SEPARATOR)
78                         mode = SEPARATORMODE;
79                 else if (group.type == BSDDIALOG_RADIOLIST)
80                         mode = RADIOLISTMODE;
81                 else if (group.type == BSDDIALOG_CHECKLIST)
82                         mode = CHECKLISTMODE;
83         }
84
85         return (mode);
86 }
87
88 static int
89 build_privatemenu(struct bsddialog_conf *conf, struct privatemenu *m,
90     enum menumode mode, unsigned int ngroups,
91     struct bsddialog_menugroup *groups)
92 {
93         bool onetrue;
94         int i, j, abs;
95         unsigned int maxsepstr, maxprefix, selectorlen, maxdepth;
96         unsigned int maxname, maxdesc;
97         struct bsddialog_menuitem *item;
98         struct privateitem *pritem;
99
100         /* nitems and fault checks */
101         CHECK_ARRAY(ngroups, groups);
102         m->nitems = 0;
103         for (i = 0; i < (int)ngroups; i++) {
104                 CHECK_ARRAY(groups[i].nitems, groups[i].items);
105                 m->nitems += (int)groups[i].nitems;
106         }
107
108         /* alloc and set private items */
109         m->pritems = calloc(m->nitems, sizeof (struct privateitem));
110         if (m->pritems == NULL)
111                 RETURN_ERROR("Cannot allocate memory for internal menu items");
112         m->hasbottomdesc = false;
113         abs = 0;
114         for (i = 0; i < (int)ngroups; i++) {
115                 onetrue = false;
116                 for (j = 0; j < (int)groups[i].nitems; j++) {
117                         item = &groups[i].items[j];
118                         pritem = &m->pritems[abs];
119
120                         if (getmode(mode, groups[i]) == MENUMODE) {
121                                 m->pritems[abs].on = false;
122                         } else if (getmode(mode, groups[i]) == RADIOLISTMODE) {
123                                 m->pritems[abs].on = onetrue ? false : item->on;
124                                 if (m->pritems[abs].on)
125                                         onetrue = true;
126                         } else { /* CHECKLISTMODE */
127                                 m->pritems[abs].on = item->on;
128                         }
129                         pritem->group = i;
130                         pritem->index = j;
131                         pritem->type = getmode(mode, groups[i]);
132
133                         pritem->prefix = CHECK_STR(item->prefix);
134                         pritem->depth = item->depth;
135                         pritem->name = CHECK_STR(item->name);
136                         pritem->desc = CHECK_STR(item->desc);
137                         pritem->bottomdesc = CHECK_STR(item->bottomdesc);
138                         if (item->bottomdesc != NULL)
139                                 m->hasbottomdesc = true;
140
141                         mbtowc(&pritem->shortcut, conf->menu.no_name ?
142                             pritem->desc : pritem->name, MB_CUR_MAX);
143
144                         abs++;
145                 }
146         }
147
148         /* positions */
149         m->xselector = m->xname = m->xdesc = m->line = 0;
150         maxsepstr = maxprefix = selectorlen = maxdepth = maxname = maxdesc = 0;
151         for (i = 0; i < m->nitems; i++) {
152                 if (m->pritems[i].type == RADIOLISTMODE ||
153                     m->pritems[i].type == CHECKLISTMODE)
154                         selectorlen = 4;
155
156                 if (m->pritems[i].type == SEPARATORMODE) {
157                         maxsepstr = MAX(maxsepstr,
158                             strcols(m->pritems[i].name) +
159                             strcols(m->pritems[i].desc));
160                         continue;
161                 }
162
163                 maxprefix = MAX(maxprefix, strcols(m->pritems[i].prefix));
164                 maxdepth  = MAX(maxdepth, m->pritems[i].depth);
165                 maxname   = MAX(maxname, strcols(m->pritems[i].name));
166                 maxdesc   = MAX(maxdesc, strcols(m->pritems[i].desc));
167         }
168         maxname = conf->menu.no_name ? 0 : maxname;
169         maxdesc = conf->menu.no_desc ? 0 : maxdesc;
170
171         m->xselector = maxprefix + (maxprefix != 0 ? 1 : 0);
172         m->xname = m->xselector + selectorlen;
173         m->xdesc = maxdepth + m->xname + maxname;
174         m->xdesc += (maxname != 0 ? 1 : 0);
175         m->line = MAX(maxsepstr + 3, m->xdesc + maxdesc);
176
177         return (0);
178 }
179
180 static void
181 set_return_on(struct privatemenu *m, struct bsddialog_menugroup *groups)
182 {
183         int i;
184         struct privateitem *pritem;
185
186         for(i = 0; i < m->nitems; i++) {
187                 if (m->pritems[i].type == SEPARATORMODE)
188                         continue;
189                 pritem = &m->pritems[i];
190                 groups[pritem->group].items[pritem->index].on = pritem->on;
191         }
192 }
193
194 static int getprev(struct privateitem *pritems, int abs)
195 {
196         int i;
197
198         for (i = abs - 1; i >= 0; i--) {
199                 if (pritems[i].type == SEPARATORMODE)
200                         continue;
201                 return (i);
202         }
203
204         return (abs);
205 }
206
207 static int getnext(int npritems, struct privateitem *pritems, int abs)
208 {
209         int i;
210
211         for (i = abs + 1; i < npritems; i++) {
212                 if (pritems[i].type == SEPARATORMODE)
213                         continue;
214                 return (i);
215         }
216
217         return (abs);
218 }
219
220 static int
221 getfirst_with_default(int npritems, struct privateitem *pritems, int ngroups,
222     struct bsddialog_menugroup *groups, int *focusgroup, int *focusitem)
223 {
224         int i, abs;
225
226         if ((abs =  getnext(npritems, pritems, -1)) < 0)
227                 return (abs);
228
229         if (focusgroup == NULL || focusitem == NULL)
230                 return (abs);
231         if (*focusgroup < 0 || *focusgroup >= ngroups)
232                 return (abs);
233         if (groups[*focusgroup].type == BSDDIALOG_SEPARATOR)
234                 return (abs);
235         if (*focusitem < 0 || *focusitem >= (int)groups[*focusgroup].nitems)
236                 return (abs);
237
238         for (i = abs; i < npritems; i++) {
239                 if (pritems[i].group == *focusgroup &&
240                     pritems[i].index == *focusitem)
241                         return (i);
242         }
243
244         return (abs);
245 }
246
247 static int
248 getfastnext(int menurows, int npritems, struct privateitem *pritems, int abs)
249 {
250         int a, start, i;
251
252         start = abs;
253         i = menurows;
254         do {
255                 a = abs;
256                 abs = getnext(npritems, pritems, abs);
257                 i--;
258         } while (abs != a && abs < start + menurows && i > 0);
259
260         return (abs);
261 }
262
263 static int
264 getfastprev(int menurows, struct privateitem *pritems, int abs)
265 {
266         int a, start, i;
267
268         start = abs;
269         i = menurows;
270         do {
271                 a = abs;
272                 abs = getprev(pritems, abs);
273                 i--;
274         } while (abs != a && abs > start - menurows && i > 0);
275
276         return (abs);
277 }
278
279 static int
280 getnextshortcut(int npritems, struct privateitem *pritems, int abs, wint_t key)
281 {
282         int i, next;
283
284         next = -1;
285         for (i = 0; i < npritems; i++) {
286                 if (pritems[i].type == SEPARATORMODE)
287                         continue;
288                 if (pritems[i].shortcut == (wchar_t)key) {
289                         if (i > abs)
290                                 return (i);
291                         if (i < abs && next == -1)
292                                 next = i;
293                 }
294         }
295
296         return (next != -1 ? next : abs);
297 }
298
299 static void drawseparators(struct bsddialog_conf *conf, struct privatemenu *m)
300 {
301         int i, linech, realw, labellen;
302         const char *desc, *name;
303
304         for (i = 0; i < m->nitems; i++) {
305                 if (m->pritems[i].type != SEPARATORMODE)
306                         continue;
307                 if (conf->no_lines == false) {
308                         wattron(m->pad, t.menu.desccolor);
309                         linech = conf->ascii_lines ? '-' : ACS_HLINE;
310                         mvwhline(m->pad, i, 0, linech, m->line);
311                         wattroff(m->pad, t.menu.desccolor);
312                 }
313                 name = m->pritems[i].name;
314                 desc = m->pritems[i].desc;
315                 realw = m->xe - m->xs;
316                 labellen = strcols(name) + strcols(desc) + 1;
317                 wmove(m->pad, i, (labellen < realw) ? realw/2 - labellen/2 : 0);
318                 wattron(m->pad, t.menu.sepnamecolor);
319                 waddstr(m->pad, name);
320                 wattroff(m->pad, t.menu.sepnamecolor);
321                 if (strcols(name) > 0 && strcols(desc) > 0)
322                         waddch(m->pad, ' ');
323                 wattron(m->pad, t.menu.sepdesccolor);
324                 waddstr(m->pad, desc);
325                 wattroff(m->pad, t.menu.sepdesccolor);
326         }
327 }
328
329 static void
330 drawitem(struct bsddialog_conf *conf, struct privatemenu *m, int y, bool focus)
331 {
332         int colordesc, colorname, colorshortcut;
333         struct privateitem *pritem;
334
335         pritem = &m->pritems[y];
336
337         /* prefix */
338         wattron(m->pad, focus ? t.menu.f_prefixcolor : t.menu.prefixcolor);
339         mvwaddstr(m->pad, y, 0, pritem->prefix);
340         wattroff(m->pad, focus ? t.menu.f_prefixcolor : t.menu.prefixcolor);
341
342         /* selector */
343         wmove(m->pad, y, m->xselector);
344         wattron(m->pad, focus ? t.menu.f_selectorcolor : t.menu.selectorcolor);
345         if (pritem->type == CHECKLISTMODE)
346                 wprintw(m->pad, "[%c]", pritem->on ? 'X' : ' ');
347         if (pritem->type == RADIOLISTMODE)
348                 wprintw(m->pad, "(%c)", pritem->on ? '*' : ' ');
349         wattroff(m->pad, focus ? t.menu.f_selectorcolor : t.menu.selectorcolor);
350
351         /* name */
352         colorname = focus ? t.menu.f_namecolor : t.menu.namecolor;
353         if (conf->menu.no_name == false) {
354                 wattron(m->pad, colorname);
355                 mvwaddstr(m->pad, y, m->xname + pritem->depth, pritem->name);
356                 wattroff(m->pad, colorname);
357         }
358
359         /* description */
360         if (conf->menu.no_name)
361                 colordesc = focus ? t.menu.f_namecolor : t.menu.namecolor;
362         else
363                 colordesc = focus ? t.menu.f_desccolor : t.menu.desccolor;
364
365         if (conf->menu.no_desc == false) {
366                 wattron(m->pad, colordesc);
367                 if (conf->menu.no_name)
368                         mvwaddstr(m->pad, y, m->xname + pritem->depth,
369                             pritem->desc);
370                 else
371                         mvwaddstr(m->pad, y, m->xdesc, pritem->desc);
372                 wattroff(m->pad, colordesc);
373         }
374
375         /* shortcut */
376         if (conf->menu.shortcut_buttons == false) {
377                 colorshortcut = focus ?
378                     t.menu.f_shortcutcolor : t.menu.shortcutcolor;
379                 wattron(m->pad, colorshortcut);
380                 mvwaddwch(m->pad, y, m->xname + pritem->depth, pritem->shortcut);
381                 wattroff(m->pad, colorshortcut);
382         }
383
384         /* bottom description */
385         if (m->hasbottomdesc) {
386                 move(SCREENLINES - 1, 2);
387                 clrtoeol();
388                 if (focus) {
389                         attron(t.menu.bottomdesccolor);
390                         addstr(pritem->bottomdesc);
391                         attroff(t.menu.bottomdesccolor);
392                         refresh();
393                 }
394         }
395 }
396
397 static void update_menubox(struct bsddialog_conf *conf, struct privatemenu *m)
398 {
399         int h, w;
400
401         draw_borders(conf, m->box, LOWERED);
402         getmaxyx(m->box, h, w);
403
404         if (m->nitems > (int)m->menurows) {
405                 wattron(m->box, t.dialog.arrowcolor);
406                 if (m->ypad > 0)
407                         mvwhline(m->box, 0, 2,
408                             conf->ascii_lines ? '^' : ACS_UARROW, 3);
409
410                 if ((m->ypad + (int)m->menurows) < m->nitems)
411                         mvwhline(m->box, h-1, 2,
412                             conf->ascii_lines ? 'v' : ACS_DARROW, 3);
413
414                 mvwprintw(m->box, h-1, w-6, "%3d%%",
415                     100 * (m->ypad + m->menurows) / m->nitems);
416                 wattroff(m->box, t.dialog.arrowcolor);
417         }
418 }
419
420 static int menu_size_position(struct dialog *d, struct privatemenu *m)
421 {
422         int htext, hmenu;
423
424         if (set_widget_size(d->conf, d->rows, d->cols, &d->h, &d->w) != 0)
425                 return (BSDDIALOG_ERROR);
426
427         hmenu = (int)(m->menurows == BSDDIALOG_AUTOSIZE) ?
428             (int)m->nitems : (int)m->menurows;
429         hmenu += 2; /* menu borders */
430         /*
431          * algo 1: notext = 1 (grows vertically).
432          * algo 2: notext = hmenu (grows horizontally, better for little term).
433          */
434         if (set_widget_autosize(d->conf, d->rows, d->cols, &d->h, &d->w,
435             d->text, &htext, &d->bs, hmenu, m->line + 4) != 0)
436                 return (BSDDIALOG_ERROR);
437         /* avoid menurows overflow and menurows becomes "at most menurows" */
438         if (d->h - BORDERS - htext - HBUTTONS <= 2 /* menuborders */)
439                 m->menurows = (m->nitems > 0) ? 1 : 0; /* widget_checksize() */
440         else
441                 m->menurows = MIN(d->h - BORDERS - htext - HBUTTONS, hmenu) - 2;
442
443         /*
444          * no minw=linelen to avoid big menu fault, then some col can be
445          * hidden (example portconfig www/apache24).
446          */
447         if (widget_checksize(d->h, d->w, &d->bs,
448             2 /* border box */ + MIN(m->menurows, 1), 0) != 0)
449                 return (BSDDIALOG_ERROR);
450
451         if (set_widget_position(d->conf, &d->y, &d->x, d->h, d->w) != 0)
452                 return (BSDDIALOG_ERROR);
453
454         return (0);
455 }
456
457 static int mixedlist_redraw(struct dialog *d, struct privatemenu *m)
458 {
459         if (d->built) {
460                 hide_dialog(d);
461                 refresh(); /* Important for decreasing screen */
462         }
463         m->menurows = m->apimenurows;
464         if (menu_size_position(d, m) != 0)
465                 return (BSDDIALOG_ERROR);
466         if (draw_dialog(d) != 0)
467                 return (BSDDIALOG_ERROR);
468         if (d->built)
469                 refresh(); /* Important to fix grey lines expanding screen */
470         TEXTPAD(d, 2/*bmenu*/ + m->menurows + HBUTTONS);
471
472         /* selected item in view*/
473         if (m->ypad > m->sel && m->ypad > 0)
474                 m->ypad = m->sel;
475         if ((int)(m->ypad + m->menurows) <= m->sel)
476                 m->ypad = m->sel - m->menurows + 1;
477         /* lower pad after a terminal expansion */
478         if (m->ypad > 0 && (m->nitems - m->ypad) < (int)m->menurows)
479                 m->ypad = m->nitems - m->menurows;
480
481         update_box(d->conf, m->box, d->y + d->h - 5 - m->menurows, d->x + 2,
482             m->menurows+2, d->w-4, LOWERED);
483         update_menubox(d->conf, m);
484         wnoutrefresh(m->box);
485
486         m->ys = d->y + d->h - 5 - m->menurows + 1;
487         m->ye = d->y + d->h - 5 ;
488         if (d->conf->menu.align_left || (int)m->line > d->w - 6) {
489                 m->xs = d->x + 3;
490                 m->xe = m->xs + d->w - 7;
491         } else { /* center */
492                 m->xs = d->x + 3 + (d->w-6)/2 - m->line/2;
493                 m->xe = m->xs + d->w - 5;
494         }
495         drawseparators(d->conf, m); /* uses xe - xs */
496         pnoutrefresh(m->pad, m->ypad, 0, m->ys, m->xs, m->ye, m->xe);
497
498         return (0);
499 }
500
501 static int
502 do_mixedlist(struct bsddialog_conf *conf, const char *text, int rows, int cols,
503     unsigned int menurows, enum menumode mode, unsigned int ngroups,
504     struct bsddialog_menugroup *groups, int *focuslist, int *focusitem)
505 {
506         bool loop, changeitem;
507         int i, next, retval;
508         wint_t input;
509         struct privatemenu m;
510         struct dialog d;
511
512         if (prepare_dialog(conf, text, rows, cols, &d) != 0)
513                 return (BSDDIALOG_ERROR);
514         set_buttons(&d, conf->menu.shortcut_buttons, OK_LABEL, CANCEL_LABEL);
515         if (d.conf->menu.no_name && d.conf->menu.no_desc)
516                 RETURN_ERROR("Both conf.menu.no_name and conf.menu.no_desc");
517
518         if (build_privatemenu(conf, &m, mode, ngroups, groups) != 0)
519                 return (BSDDIALOG_ERROR);
520
521         if ((m.box = newwin(1, 1, 1, 1)) == NULL)
522                 RETURN_ERROR("Cannot build WINDOW box menu");
523         wbkgd(m.box, t.dialog.color);
524         m.pad = newpad(m.nitems, m.line);
525         wbkgd(m.pad, t.dialog.color);
526
527         for (i = 0; i < m.nitems; i++)
528                 drawitem(conf, &m, i, false);
529         m.sel = getfirst_with_default(m.nitems, m.pritems, ngroups, groups,
530             focuslist, focusitem);
531         if (m.sel >= 0)
532                 drawitem(d.conf, &m, m.sel, true);
533         m.ypad = 0;
534         m.apimenurows = menurows;
535         if (mixedlist_redraw(&d, &m) != 0)
536                 return (BSDDIALOG_ERROR);
537
538         changeitem = false;
539         loop = true;
540         while (loop) {
541                 doupdate();
542                 if (get_wch(&input) == ERR)
543                         continue;
544                 switch(input) {
545                 case KEY_ENTER:
546                 case 10: /* Enter */
547                         retval = BUTTONVALUE(d.bs);
548                         if (m.sel >= 0 && m.pritems[m.sel].type == MENUMODE)
549                                 m.pritems[m.sel].on = true;
550                         loop = false;
551                         break;
552                 case 27: /* Esc */
553                         if (conf->key.enable_esc) {
554                                 retval = BSDDIALOG_ESC;
555                                 if (m.sel >= 0 &&
556                                    m.pritems[m.sel].type == MENUMODE)
557                                         m.pritems[m.sel].on = true;
558                                 loop = false;
559                         }
560                         break;
561                 case '\t': /* TAB */
562                 case KEY_RIGHT:
563                         d.bs.curr = (d.bs.curr + 1) % d.bs.nbuttons;
564                         DRAW_BUTTONS(d);
565                         break;
566                 case KEY_LEFT:
567                         d.bs.curr--;
568                         if (d.bs.curr < 0)
569                                  d.bs.curr = d.bs.nbuttons - 1;
570                         DRAW_BUTTONS(d);
571                         break;
572                 case KEY_F(1):
573                         if (conf->key.f1_file == NULL &&
574                             conf->key.f1_message == NULL)
575                                 break;
576                         if (f1help_dialog(conf) != 0)
577                                 return (BSDDIALOG_ERROR);
578                         if (mixedlist_redraw(&d, &m) != 0)
579                                 return (BSDDIALOG_ERROR);
580                         break;
581                 case KEY_RESIZE:
582                         if (mixedlist_redraw(&d, &m) != 0)
583                                 return (BSDDIALOG_ERROR);
584                         break;
585                 }
586
587                 if (m.sel < 0)
588                         continue;
589                 switch(input) {
590                 case KEY_HOME:
591                         next = getnext(m.nitems, m.pritems, -1);
592                         changeitem = next != m.sel;
593                         break;
594                 case KEY_UP:
595                         next = getprev(m.pritems, m.sel);
596                         changeitem = next != m.sel;
597                         break;
598                 case KEY_PPAGE:
599                         next = getfastprev(m.menurows, m.pritems, m.sel);
600                         changeitem = next != m.sel;
601                         break;
602                 case KEY_END:
603                         next = getprev(m.pritems, m.nitems);
604                         changeitem = next != m.sel;
605                         break;
606                 case KEY_DOWN:
607                         next = getnext(m.nitems, m.pritems, m.sel);
608                         changeitem = next != m.sel;
609                         break;
610                 case KEY_NPAGE:
611                         next = getfastnext(m.menurows, m.nitems, m.pritems, m.sel);
612                         changeitem = next != m.sel;
613                         break;
614                 case ' ': /* Space */
615                         if (m.pritems[m.sel].type == MENUMODE) {
616                                 retval = BUTTONVALUE(d.bs);
617                                 m.pritems[m.sel].on = true;
618                                 loop = false;
619                         } else if (m.pritems[m.sel].type == CHECKLISTMODE) {
620                                 m.pritems[m.sel].on = !m.pritems[m.sel].on;
621                         } else { /* RADIOLISTMODE */
622                                 for (i = m.sel - m.pritems[m.sel].index;
623                                     i < m.nitems &&
624                                     m.pritems[i].group == m.pritems[m.sel].group;
625                                     i++) {
626                                         if (i != m.sel && m.pritems[i].on) {
627                                                 m.pritems[i].on = false;
628                                                 drawitem(conf, &m, i, false);
629                                         }
630                                 }
631                                 m.pritems[m.sel].on = !m.pritems[m.sel].on;
632                         }
633                         drawitem(conf, &m, m.sel, true);
634                         pnoutrefresh(m.pad, m.ypad, 0, m.ys, m.xs, m.ye, m.xe);
635                         break;
636                 default:
637                         if (conf->menu.shortcut_buttons) {
638                                 if (shortcut_buttons(input, &d.bs)) {
639                                         DRAW_BUTTONS(d);
640                                         doupdate();
641                                         retval = BUTTONVALUE(d.bs);
642                                         if (m.pritems[m.sel].type == MENUMODE)
643                                                 m.pritems[m.sel].on = true;
644                                         loop = false;
645                                 }
646                                 break;
647                         }
648
649                         /* shourtcut items */
650                         next = getnextshortcut(m.nitems, m.pritems, m.sel,
651                             input);
652                         changeitem = next != m.sel;
653                 } /* end switch get_wch() */
654
655                 if (changeitem) {
656                         drawitem(conf, &m, m.sel, false);
657                         m.sel = next;
658                         drawitem(conf, &m, m.sel, true);
659                         if (m.ypad > m.sel && m.ypad > 0)
660                                 m.ypad = m.sel;
661                         if ((int)(m.ypad + m.menurows) <= m.sel)
662                                 m.ypad = m.sel - m.menurows + 1;
663                         update_menubox(conf, &m);
664                         wnoutrefresh(m.box);
665                         pnoutrefresh(m.pad, m.ypad, 0, m.ys, m.xs, m.ye, m.xe);
666                         changeitem = false;
667                 }
668         } /* end while(loop) */
669
670         set_return_on(&m, groups);
671
672         if (focuslist != NULL)
673                 *focuslist = m.sel < 0 ? -1 : m.pritems[m.sel].group;
674         if (focusitem !=NULL)
675                 *focusitem = m.sel < 0 ? -1 : m.pritems[m.sel].index;
676
677         if (m.hasbottomdesc && conf->clear) {
678                 move(SCREENLINES - 1, 2);
679                 clrtoeol();
680         }
681         delwin(m.pad);
682         delwin(m.box);
683         end_dialog(&d);
684         free(m.pritems);
685
686         return (retval);
687 }
688
689 /* API */
690 int
691 bsddialog_mixedlist(struct bsddialog_conf *conf, const char *text, int rows,
692     int cols, unsigned int menurows, unsigned int ngroups,
693     struct bsddialog_menugroup *groups, int *focuslist, int *focusitem)
694 {
695         int retval;
696
697         retval = do_mixedlist(conf, text, rows, cols, menurows, MIXEDLISTMODE,
698             ngroups, groups, focuslist, focusitem);
699
700         return (retval);
701 }
702
703 int
704 bsddialog_checklist(struct bsddialog_conf *conf, const char *text, int rows,
705     int cols, unsigned int menurows, unsigned int nitems,
706     struct bsddialog_menuitem *items, int *focusitem)
707 {
708         int retval, focuslist = 0;
709         struct bsddialog_menugroup group = {
710             BSDDIALOG_CHECKLIST /* unused */, nitems, items, 0};
711
712         CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */
713         retval = do_mixedlist(conf, text, rows, cols, menurows, CHECKLISTMODE,
714             1, &group, &focuslist, focusitem);
715
716         return (retval);
717 }
718
719 int
720 bsddialog_menu(struct bsddialog_conf *conf, const char *text, int rows,
721     int cols, unsigned int menurows, unsigned int nitems,
722     struct bsddialog_menuitem *items, int *focusitem)
723 {
724         int retval, focuslist = 0;
725         struct bsddialog_menugroup group = {
726             BSDDIALOG_CHECKLIST /* unused */, nitems, items, 0};
727
728         CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */
729         retval = do_mixedlist(conf, text, rows, cols, menurows, MENUMODE, 1,
730             &group, &focuslist, focusitem);
731
732         return (retval);
733 }
734
735 int
736 bsddialog_radiolist(struct bsddialog_conf *conf, const char *text, int rows,
737     int cols, unsigned int menurows, unsigned int nitems,
738     struct bsddialog_menuitem *items, int *focusitem)
739 {
740         int retval, focuslist = 0;
741         struct bsddialog_menugroup group = {
742             BSDDIALOG_RADIOLIST /* unused */, nitems, items, 0};
743
744         CHECK_ARRAY(nitems, items); /* efficiency, avoid do_mixedlist() */
745         retval = do_mixedlist(conf, text, rows, cols, menurows, RADIOLISTMODE,
746             1, &group, &focuslist, focusitem);
747
748         return (retval);
749 }