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