Initial import from FreeBSD RELENG_4:
[dragonfly.git] / gnu / lib / libdialog / radiolist.c
1 /*
2  *  radiolist.c -- implements the radiolist box
3  *
4  *  AUTHOR: Stuart Herbert - S.Herbert@sheffield.ac.uk
5  *   (from checklist.c by Savio Lam (lam836@cs.cuhk.hk))
6  *
7  *      Substantial rennovation:  12/18/95, Jordan K. Hubbard
8  *
9  *  This program is free software; you can redistribute it and/or
10  *  modify it under the terms of the GNU General Public License
11  *  as published by the Free Software Foundation; either version 2
12  *  of the License, or (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #ifndef lint
25 static const char rcsid[] =
26   "$FreeBSD: src/gnu/lib/libdialog/radiolist.c,v 1.36.2.2 2001/08/31 03:04:59 eric Exp $";
27 #endif
28
29 #include <dialog.h>
30 #include "dialog.priv.h"
31
32
33 static void print_item(WINDOW *win, char *tag, char *item, int status, int choice, int selected, dialogMenuItem *me);
34
35 #define DREF(di, item)          ((di) ? &((di)[(item)]) : NULL)
36
37 static int list_width, check_x, item_x;
38
39
40 /*
41  * Display a dialog box with a list of options that can be turned on or off
42  */
43 int
44 dialog_radiolist(unsigned char *title, unsigned char *prompt, int height, int width, int list_height,
45                  int cnt, void *it, unsigned char *result)
46 {
47     int i, j, x, y, cur_x, cur_y, old_x, old_y, box_x, box_y, key = 0, button,
48         choice, l, k, scroll, max_choice, *status, item_no = 0, was_on = 0;
49     int redraw_menu = FALSE, cursor_reset = FALSE;
50     int rval = 0, onlist = 1, ok_space, cancel_space;
51     char okButton, cancelButton;
52     WINDOW *dialog, *list;
53     unsigned char **items = NULL;
54     dialogMenuItem *ditems;
55     
56     /* Allocate space for storing item on/off status */
57     if ((status = alloca(sizeof(int) * abs(cnt))) == NULL) {
58         endwin();
59         fprintf(stderr, "\nCan't allocate memory in dialog_radiolist().\n");
60         exit(-1);
61     }
62     
63 draw:
64     button = choice = scroll = 0;
65     /* Previous calling syntax, e.g. just a list of strings? */
66     if (cnt >= 0) {
67         items = it;
68         ditems = NULL;
69         item_no = cnt;
70         /* Initializes status */
71         for (i = 0; i < item_no; i++) {
72             status[i] = !strcasecmp(items[i*3 + 2], "on");
73             if (status[i]) {
74                 if (was_on)
75                     status[i] = FALSE;
76                 else
77                     was_on = 1;
78             }
79         }
80     }
81     /* It's the new specification format - fake the rest of the code out */
82     else {
83         item_no = abs(cnt);
84         ditems = it;
85         if (!items)
86             items = (unsigned char **)alloca((item_no * 3) * sizeof(unsigned char *));
87         /* Initializes status */
88         for (i = 0; i < item_no; i++) {
89             status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
90             if (status[i]) {
91                 if (was_on)
92                     status[i] = FALSE;
93                 else
94                     was_on = 1;
95             }
96             items[i*3] = ditems[i].prompt;
97             items[i*3 + 1] = ditems[i].title;
98             items[i*3 + 2] = status[i] ? "on" : "off";
99         }
100     }
101     max_choice = MIN(list_height, item_no);
102     
103     check_x = 0;
104     item_x = 0;
105     /* Find length of longest item in order to center radiolist */
106     for (i = 0; i < item_no; i++) {
107         l = strlen(items[i * 3]);
108         for (j = 0; j < item_no; j++) {
109             k = strlen(items[j * 3 + 1]);
110             check_x = MAX(check_x, l + k + 6);
111         }
112         item_x = MAX(item_x, l);
113     }
114     if (height < 0)
115         height = strheight(prompt) + list_height + 4 + 2;
116     if (width < 0) {
117         i = strwidth(prompt);
118         j = ((title != NULL) ? strwidth(title) : 0);
119         width = MAX(i, j);
120         width = MAX(width, check_x + 4) + 4;
121     }
122     width = MAX(width, 24);
123     
124     if (width > COLS)
125         width = COLS;
126     if (height > LINES)
127         height = LINES;
128     /* center dialog box on screen */
129     x = DialogX ? DialogX : (COLS - width) / 2;
130     y = DialogY ? DialogY : (LINES - height) / 2;
131
132 #ifdef HAVE_NCURSES
133     if (use_shadow)
134         draw_shadow(stdscr, y, x, height, width);
135 #endif
136     dialog = newwin(height, width, y, x);
137     if (dialog == NULL) {
138         endwin();
139         fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height, width, y, x);
140         return -1;
141     }
142     keypad(dialog, TRUE);
143     
144     draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
145     wattrset(dialog, border_attr);
146     wmove(dialog, height - 3, 0);
147     waddch(dialog, ACS_LTEE);
148     for (i = 0; i < width - 2; i++)
149         waddch(dialog, ACS_HLINE);
150     wattrset(dialog, dialog_attr);
151     waddch(dialog, ACS_RTEE);
152     wmove(dialog, height - 2, 1);
153     for (i = 0; i < width - 2; i++)
154         waddch(dialog, ' ');
155     
156     if (title != NULL) {
157         wattrset(dialog, title_attr);
158         wmove(dialog, 0, (width - strlen(title)) / 2 - 1);
159         waddch(dialog, ' ');
160         waddstr(dialog, title);
161         waddch(dialog, ' ');
162     }
163     wattrset(dialog, dialog_attr);
164     wmove(dialog, 1, 2);
165     print_autowrap(dialog, prompt, height - 1, width - 2, width, 1, 2, TRUE, FALSE);
166     
167     list_width = width - 6;
168     getyx(dialog, cur_y, cur_x);
169     box_y = cur_y + 1;
170     box_x = (width - list_width) / 2 - 1;
171     
172     /* create new window for the list */
173     list = subwin(dialog, list_height, list_width, y + box_y + 1, x + box_x + 1);
174     if (list == NULL) {
175         delwin(dialog);
176         endwin();
177         fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", list_height, list_width,
178                 y + box_y + 1,x + box_x + 1);
179         return -1;
180     }
181     keypad(list, TRUE);
182     
183     /* draw a box around the list items */
184     draw_box(dialog, box_y, box_x, list_height+2, list_width+2, menubox_border_attr, menubox_attr);
185     
186     check_x = (list_width - check_x) / 2;
187     item_x = check_x + item_x + 6;
188     
189     /* Print the list */
190     for (i = 0; i < max_choice; i++)
191         print_item(list, items[i * 3], items[i * 3 + 1], status[i], i, i == choice, DREF(ditems, i));
192     wnoutrefresh(list);
193     print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
194     
195     display_helpline(dialog, height-1, width);
196     
197     x = width/ 2 - 11;
198     y = height - 2;
199     if (ditems && result) {
200         cancelButton = toupper(ditems[CANCEL_BUTTON].prompt[0]);
201         print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
202                      ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : FALSE);
203         okButton = toupper(ditems[OK_BUTTON].prompt[0]);
204         print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
205                      ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : TRUE);
206     }
207     else {
208         cancelButton = 'C';
209         print_button(dialog, "Cancel", y, x + 14, FALSE);
210         okButton = 'O';
211         print_button(dialog, "  OK  ", y, x, TRUE);
212     }
213     wnoutrefresh(dialog);
214     wmove(list, choice, check_x+1);
215     wrefresh(list);
216
217     while (key != ESC) {
218         key = wgetch(dialog);
219         
220         /* See if its the short-cut to "OK" */
221         if (toupper(key) == okButton) {
222             if (ditems) {
223                 if (result && ditems[OK_BUTTON].fire) {
224                     int st;
225                     WINDOW *save;
226
227                     save = dupwin(newscr);
228                     st = ditems[OK_BUTTON].fire(&ditems[OK_BUTTON]);
229                     if (st & DITEM_RESTORE) {
230                         touchwin(save);
231                         wrefresh(save);
232                     }
233                     delwin(save);
234                 }
235             }
236             else if (result) {
237                 *result = '\0';
238                 for (i = 0; i < item_no; i++) {
239                     if (status[i]) {
240                         strcat(result, items[i*3]);
241                         break;
242                     }
243                 }
244             }
245             rval = 0;
246             key = ESC;
247             break;
248         }
249
250         /* Shortcut to cancel */
251         if (toupper(key) == cancelButton) {
252             if (ditems && result && ditems[CANCEL_BUTTON].fire) {
253                 int st;
254                 WINDOW *save;
255
256                 save = dupwin(newscr);
257                 st = ditems[CANCEL_BUTTON].fire(&ditems[CANCEL_BUTTON]);
258                 if (st & DITEM_RESTORE) {
259                     touchwin(save);
260                     wrefresh(save);
261                 }
262                 delwin(save);
263             }
264             rval = 1;
265             key = ESC;
266             break;
267         }
268         
269         /* Check if key pressed matches first character of any item tag in list */
270         for (i = 0; i < max_choice; i++)
271             if (key != ' ' && toupper(key) == toupper(items[(scroll + i) * 3][0]))
272                 break;
273
274         if (i < max_choice || (key >= '1' && key <= MIN('9', '0' + max_choice)) ||
275             KEY_IS_UP(key) || KEY_IS_DOWN(key) || ((key == ' ' || key == '\r' || key == '\n') && onlist == 1)) {
276
277             /* if moving from buttons to the list, reset and redraw buttons */
278             if (!onlist) {
279                 onlist = 1;
280                 button = 0;
281
282                 if (ditems && result ) {
283                     print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
284                         ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
285                     print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
286                         ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
287                 }
288                 else {
289                     print_button(dialog, "Cancel", y, x + 14, button);
290                     print_button(dialog, "  OK  ", y, x, !button);
291                 }
292             }
293             wmove(list, choice, check_x+1);
294             wnoutrefresh(dialog);
295             wrefresh(list);
296
297             if (key >= '1' && key <= MIN('9', '0' + max_choice))
298                 i = key - '1';
299             else if (KEY_IS_UP(key)) {
300                 if (!choice) {
301                     if (scroll) {
302                         /* Scroll list down */
303                         getyx(dialog, cur_y, cur_x);    /* Save cursor position */
304                         if (list_height > 1) {
305                             /* De-highlight current first item before scrolling down */
306                             print_item(list, items[scroll*3], items[scroll*3 + 1], status[scroll], 0,
307                                        FALSE, DREF(ditems, scroll));
308                             scrollok(list, TRUE);
309                             wscrl(list, -1);
310                             scrollok(list, FALSE);
311                         }
312                         scroll--;
313                         print_item(list, items[scroll*3], items[scroll*3 + 1], status[scroll], 0,
314                                    TRUE, DREF(ditems, scroll));
315                         print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
316                         wmove(list, choice, check_x+1);
317                         wnoutrefresh(dialog);
318                         wrefresh(list);
319                     }
320                     continue;    /* wait for another key press */
321                 }
322                 else
323                     i = choice - 1;
324             }
325             else if (KEY_IS_DOWN(key)) {
326                 if (choice == max_choice - 1) {
327                     if (scroll + choice < item_no - 1) {
328                         /* Scroll list up */
329                         getyx(dialog, cur_y, cur_x);    /* Save cursor position */
330                         if (list_height > 1) {
331                             /* De-highlight current last item before scrolling up */
332                             print_item(list, items[(scroll + max_choice - 1) * 3],
333                                        items[(scroll + max_choice - 1) * 3 + 1],
334                                        status[scroll + max_choice - 1], max_choice - 1,
335                                        FALSE, DREF(ditems, scroll + max_choice - 1));
336                             scrollok(list, TRUE);
337                             scroll(list);
338                             scrollok(list, FALSE);
339                         }
340                         scroll++;
341                         print_item(list, items[(scroll + max_choice - 1) * 3],
342                                    items[(scroll + max_choice - 1) * 3 + 1],
343                                    status[scroll + max_choice - 1], max_choice - 1,
344                                    TRUE, DREF(ditems, scroll + max_choice - 1));
345                         print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
346                         wmove(list, choice, check_x+1);
347                         wnoutrefresh(dialog);
348                         wrefresh(list);
349                     }
350                     continue;    /* wait for another key press */
351                 }
352                 else
353                     i = choice + 1;
354             }
355             else if ((key == ' ' || key == '\r' || key == '\n') && onlist) {    /* Toggle item status */
356                 getyx(list, old_y, old_x);     /* Save cursor position */
357                 if (status[scroll + choice])
358                     continue;
359                 else if (ditems) {
360                     if (ditems[scroll + choice].fire) {
361                         int st;
362                         WINDOW *save;
363
364                         save = dupwin(newscr);
365                         st = ditems[scroll + choice].fire(&ditems[scroll + choice]);
366                         if (st & DITEM_RESTORE) {
367                             touchwin(save);
368                             wrefresh(save);
369                         }
370                         delwin(save);
371                         if (st & DITEM_REDRAW) {
372                             wclear(list);
373                             for (i = 0; i < item_no; i++)
374                                 status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
375
376                             for (i = 0; i < max_choice; i++) {
377                                 print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1],
378                                            status[scroll + i], i, i == choice,
379                                            DREF(ditems, scroll + i));
380                             }
381 /*                          wmove(list, old_y, old_x);*/  /* Restore cursor to previous position */
382 /*                          wrefresh(list); */
383                         }
384                         if (st & DITEM_LEAVE_MENU) {
385                             /* Allow a fire action to take us out of the menu */
386                             key = ESC;
387                             break;
388                         }
389                         else if (st & DITEM_RECREATE) {
390                             delwin(list);
391                             delwin(dialog);
392                             dialog_clear();
393                             goto draw;
394                         }
395                     }
396                     for (i = 0; i < item_no; i++)
397                         status[i] = ditems[i].checked ? ditems[i].checked(&ditems[i]) : FALSE;
398                 }
399                 else {
400                     for (i = 0; i < item_no; i++)
401                         status[i] = 0;
402                     status[scroll + choice] = TRUE;
403                 }
404                 for (i = 0; i < max_choice; i++)
405                     print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1],
406                                status[scroll + i], i, i == choice, DREF(ditems, scroll + i));
407                 wmove(list, choice, check_x+1);  /* Restore cursor position */
408                 wrefresh(list);
409                 continue;    /* wait for another key press */
410             }
411             
412             if (i != choice) {
413                 /* De-highlight current item */
414                 print_item(list, items[(scroll + choice) * 3], items[(scroll + choice) * 3 +1],
415                            status[scroll + choice], choice, FALSE, DREF(ditems, scroll + choice));
416                 /* Highlight new item */
417                 choice = i;
418                 print_item(list, items[(scroll + choice) * 3], items[(scroll + choice) * 3 + 1],
419                            status[scroll + choice], choice, TRUE, DREF(ditems, scroll + choice));
420                 wmove(list, choice, check_x+1);  /* Restore cursor position */
421                 wrefresh(list);
422             }
423             continue;    /* wait for another key press */
424         }
425         
426         switch (key) {
427         case KEY_PPAGE:
428             if (scroll > height-4)              /* can we go up? */
429                 scroll -= (height-4);
430             else
431                 scroll = 0;
432             redraw_menu = TRUE;
433             if (!onlist) {
434                 onlist = 1;
435                 button = 0;
436             }
437             break;
438             
439         case KEY_NPAGE:
440             if (scroll + list_height >= item_no-1 - list_height) { /* can we go down a full page? */
441                 scroll = item_no - list_height;
442                 if (scroll < 0)
443                     scroll = 0;
444             }
445             else
446                 scroll += list_height;
447             redraw_menu = TRUE;
448             if (!onlist) {
449                 onlist = 1;
450                 button = 0;
451             }
452             break;
453             
454         case KEY_HOME:
455             scroll = 0;
456             choice = 0;
457             redraw_menu = TRUE;
458             cursor_reset = TRUE;
459             onlist = 1;
460             break;
461             
462         case KEY_END:
463             scroll = item_no - list_height;
464             if (scroll < 0)
465                 scroll = 0;
466             choice = max_choice - 1;
467             redraw_menu = TRUE;
468             cursor_reset = TRUE;
469             onlist = 1;
470             break;
471             
472         case TAB:
473         case KEY_BTAB:
474             /* move to next component */
475             if (onlist) {      /* on list, next is ok button */
476                 onlist = 0;
477                 if (ditems && result)
478                     ok_space = 1;
479                 else
480                     ok_space = 3;
481                 wmove(dialog, y, x + ok_space);
482                 wrefresh(dialog);
483                 break;
484             }
485             else if (button) {      /* on cancel button, next is list */
486                 button = 0;
487                 onlist = 1;
488                 redraw_menu = TRUE;
489                 break;
490             }
491             /* on ok button, next is cancel button, same as left/right case */
492
493         case KEY_LEFT:
494         case KEY_RIGHT:
495             onlist = 0;
496             button = !button;
497             if (ditems && result) {
498                 print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
499                              ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
500                 print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
501                              ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
502                 ok_space = 1;
503                 cancel_space = strlen(ditems[OK_BUTTON].prompt) + 6;
504             }
505             else {
506                 print_button(dialog, "Cancel", y, x + 14, button);
507                 print_button(dialog, "  OK  ", y, x, !button);
508                 ok_space = 3;
509                 cancel_space = 15;
510             }
511             if (button)
512                 wmove(dialog, y, x + cancel_space);
513             else
514                 wmove(dialog, y, x + ok_space);
515             wrefresh(dialog);
516             break;
517
518         case ' ':           
519         case '\r':
520         case '\n':
521             if (!onlist) {
522                 if (ditems) {
523                     if (result && ditems[button ? CANCEL_BUTTON : OK_BUTTON].fire) {
524                         int st;
525                         WINDOW *save;
526
527                         save = dupwin(newscr);
528                         st = ditems[button ? CANCEL_BUTTON : OK_BUTTON].fire(&ditems[button ? CANCEL_BUTTON : OK_BUTTON]);
529                         if (st & DITEM_RESTORE) {
530                             touchwin(save);
531                             wrefresh(save);
532                         }
533                         delwin(save);
534                     }
535                 }
536                 else if (result) {
537                     *result = '\0';
538                     for (i = 0; i < item_no; i++) {
539                         if (status[i]) {
540                             strcpy(result, items[i*3]);
541                             break;
542                         }
543                     }
544                 }
545                 rval = button;
546                 key = ESC;
547                 break;
548             }
549             
550         case ESC:
551             rval = -1;
552             break;
553             
554         case KEY_F(1):
555         case '?':
556             display_helpfile();
557             break;
558         }
559         
560         if (redraw_menu) {
561             getyx(list, old_y, old_x);
562             wclear(list);
563             for (i = 0; i < max_choice; i++)
564                 print_item(list, items[(scroll + i) * 3], items[(scroll + i) * 3 + 1], status[scroll + i],
565                            i, i == choice, DREF(ditems, scroll + i));
566             print_arrows(dialog, scroll, list_height, item_no, box_x, box_y, check_x + 4, cur_x, cur_y);
567
568             /* redraw buttons to fix highlighting */
569             if (ditems && result) {
570                 print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5,
571                         ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
572                 print_button(dialog, ditems[OK_BUTTON].prompt, y, x,
573                         ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
574             }
575             else {
576                 print_button(dialog, "Cancel", y, x + 14, button);
577                 print_button(dialog, "  OK  ", y, x, !button);
578             }
579             wnoutrefresh(dialog);
580             if (cursor_reset) {
581                 wmove(list, choice, check_x+1);
582                 cursor_reset = FALSE;
583             }
584             else {
585                 wmove(list, old_y, old_x);
586             }
587             wrefresh(list);
588             redraw_menu = FALSE;
589         }
590     }
591     
592     delwin(list);
593     delwin(dialog);
594     return rval;    /* ESC pressed */
595 }
596
597 /*
598  * Print list item
599  */
600 static void
601 print_item(WINDOW *win, char *tag, char *item, int status, int choice, int selected, dialogMenuItem *me)
602 {
603     int i;
604     
605     /* Clear 'residue' of last item */
606     wattrset(win, menubox_attr);
607     wmove(win, choice, 0);
608     for (i = 0; i < list_width; i++)
609         waddch(win, ' ');
610     wmove(win, choice, check_x);
611     wattrset(win, selected ? check_selected_attr : check_attr);
612     wprintw(win, "%c%c%c", me && me->lbra ? me->lbra : '(',
613             status ? me && me->mark ? me->mark : '*' : ' ',
614             me && me->rbra ? me->rbra : ')');
615     wattrset(win, menubox_attr);
616     waddch(win, ' ');
617     wattrset(win, selected ? tag_key_selected_attr : tag_key_attr);
618     waddch(win, tag[0]);
619     wattrset(win, selected ? tag_selected_attr : tag_attr);
620     waddstr(win, tag + 1);
621     wmove(win, choice, item_x);
622     wattrset(win, selected ? item_selected_attr : item_attr);
623     waddstr(win, item);
624     /* If have a selection handler for this, call it */
625     if (me && me->selected) {
626         wrefresh(win);
627         me->selected(me, selected);
628     }
629 }
630 /* End of print_item() */