Initial import from FreeBSD RELENG_4:
[dragonfly.git] / gnu / lib / libdialog / menubox.c
1 /*
2  *  menubox.c -- implements the menu box
3  *
4  *  AUTHOR: Savio Lam (lam836@cs.cuhk.hk)
5  *
6  *      Substantial rennovation:  12/18/95, Jordan K. Hubbard
7  *
8  *  This program is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU General Public License
10  *  as published by the Free Software Foundation; either version 2
11  *  of the License, or (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21  */
22
23 #ifndef lint
24 static const char rcsid[] =
25   "$FreeBSD: src/gnu/lib/libdialog/menubox.c,v 1.35.2.1 2001/07/31 20:34:00 eric Exp $";
26 #endif
27
28 #include <dialog.h>
29 #include "dialog.priv.h"
30 #include <ncurses.h>
31
32 static void print_item(WINDOW *win, unsigned char *tag, unsigned char *item, int choice, int selected, dialogMenuItem *me, int menu_width, int tag_x, int item_x);
33
34 #define DREF(di, item)          ((di) ? &((di)[(item)]) : NULL)
35
36 /*
37  * Display a menu for choosing among a number of options
38  */
39 int
40 dialog_menu(unsigned char *title, unsigned char *prompt, int height, int width, int menu_height, int cnt, void *it, unsigned char *result, int *ch, int *sc)
41 {
42     int i, j, x, y, cur_x, cur_y, box_x, box_y, key = 0, button, choice,
43         l, k, scroll, max_choice, item_no, redraw_menu = FALSE;
44     char okButton, cancelButton;
45     int rval = 0, ok_space, cancel_space;
46     WINDOW *dialog, *menu;
47     unsigned char **items = NULL;
48     dialogMenuItem *ditems;
49     int menu_width, tag_x, item_x;
50     
51 draw:
52     choice = ch ? *ch : 0;
53     scroll = sc ? *sc : 0;
54     button = 0;
55     
56     /* If item_no is a positive integer, use old item specification format */
57     if (cnt >= 0) {
58         items = it;
59         ditems = NULL;
60         item_no = cnt;
61     }
62     /* It's the new specification format - fake the rest of the code out */
63     else {
64         item_no = abs(cnt);
65         ditems = it;
66         if (!items)
67             items = (unsigned char **)alloca((item_no * 2) * sizeof(unsigned char *));
68         
69         /* Initializes status */
70         for (i = 0; i < item_no; i++) {
71             items[i*2] = ditems[i].prompt;
72             items[i*2 + 1] = ditems[i].title;
73         }
74     }
75     max_choice = MIN(menu_height, item_no);
76     
77     tag_x = 0;
78     item_x = 0;
79     /* Find length of longest item in order to center menu */
80     for (i = 0; i < item_no; i++) {
81         l = strlen(items[i * 2]);
82         for (j = 0; j < item_no; j++) {
83             k = strlen(items[j * 2 + 1]);
84             tag_x = MAX(tag_x, l + k + 2);
85         }
86         item_x = MAX(item_x, l);
87     }
88     if (height < 0)
89         height = strheight(prompt) + menu_height + 4 + 2;
90     if (width < 0) {
91         i = strwidth(prompt);
92         j = ((title != NULL) ? strwidth(title) : 0);
93         width = MAX(i, j);
94         width = MAX(width, tag_x + 4) + 4;
95     }
96     width = MAX(width, 24);
97     
98     if (width > COLS)
99         width = COLS;
100     if (height > LINES)
101         height = LINES;
102     /* center dialog box on screen */
103     x = DialogX ? DialogX : (COLS - width) / 2;
104     y = DialogY ? DialogY : (LINES - height) / 2;
105     
106 #ifdef HAVE_NCURSES
107     if (use_shadow)
108         draw_shadow(stdscr, y, x, height, width);
109 #endif
110     dialog = newwin(height, width, y, x);
111     if (dialog == NULL) {
112         endwin();
113         fprintf(stderr, "\nnewwin(%d,%d,%d,%d) failed, maybe wrong dims\n", height, width, y, x);
114         return -1;
115     }
116     keypad(dialog, TRUE);
117     
118     draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr);
119     wattrset(dialog, border_attr);
120     wmove(dialog, height - 3, 0);
121     waddch(dialog, ACS_LTEE);
122     for (i = 0; i < width - 2; i++)
123         waddch(dialog, ACS_HLINE);
124     wattrset(dialog, dialog_attr);
125     waddch(dialog, ACS_RTEE);
126     wmove(dialog, height - 2, 1);
127     for (i = 0; i < width - 2; i++)
128         waddch(dialog, ' ');
129     
130     if (title != NULL) {
131         wattrset(dialog, title_attr);
132         wmove(dialog, 0, (width - strlen(title)) / 2 - 1);
133         waddch(dialog, ' ');
134         waddstr(dialog, title);
135         waddch(dialog, ' ');
136     }
137     wattrset(dialog, dialog_attr);
138     wmove(dialog, 1, 2);
139     print_autowrap(dialog, prompt, height - 1, width - 2, width, 1, 2, TRUE, FALSE);
140     
141     menu_width = width - 6;
142     getyx(dialog, cur_y, cur_x);
143     box_y = cur_y + 1;
144     box_x = (width - menu_width) / 2 - 1;
145     
146     /* create new window for the menu */
147     menu = subwin(dialog, menu_height, menu_width, y + box_y + 1, x + box_x + 1);
148     if (menu == NULL) {
149         delwin(dialog);
150         endwin();
151         fprintf(stderr, "\nsubwin(dialog,%d,%d,%d,%d) failed, maybe wrong dims\n", menu_height, menu_width,
152                 y + box_y + 1, x + box_x + 1);
153         return -1;
154     }
155     keypad(menu, TRUE);
156     
157     /* draw a box around the menu items */
158     draw_box(dialog, box_y, box_x, menu_height+2, menu_width+2, menubox_border_attr, menubox_attr);
159     
160     tag_x = menu_width > tag_x + 1 ? (menu_width - tag_x) / 2 : 1;
161     item_x = menu_width > item_x + 4 ? tag_x + item_x + 2 : menu_width - 3;
162     
163     /* Print the menu */
164     for (i = 0; i < max_choice; i++)
165         print_item(menu, items[(scroll + i) * 2], items[(scroll + i) * 2 + 1], i, i == choice, DREF(ditems, scroll + i), menu_width, tag_x, item_x);
166     wnoutrefresh(menu);
167     print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
168     
169     display_helpline(dialog, height - 1, width);
170     
171     x = width / 2 - 11;
172     y = height - 2;
173     
174     if (ditems && result) {
175         cancelButton = toupper(ditems[CANCEL_BUTTON].prompt[0]);
176         print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5, ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : FALSE);
177         okButton = toupper(ditems[OK_BUTTON].prompt[0]);
178         print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : TRUE);
179     }
180     else {
181         cancelButton = 'C';
182         print_button(dialog, "Cancel", y, x + 14, FALSE);
183         okButton = 'O';
184         print_button(dialog, "  OK  ", y, x, TRUE);
185     }
186     
187     wrefresh(dialog);
188     while (key != ESC) {
189         key = wgetch(dialog);
190         
191         /* Shortcut to OK? */
192         if (toupper(key) == okButton) {
193             if (ditems) {
194                 if (result && ditems[OK_BUTTON].fire) {
195                     int status;
196                     WINDOW *save;
197
198                     save = dupwin(newscr);
199                     status = ditems[OK_BUTTON].fire(&ditems[OK_BUTTON]);
200                     if (status & DITEM_RESTORE) {
201                         touchwin(save);
202                         wrefresh(save);
203                     }
204                     delwin(save);
205                 }
206             }
207             else if (result)
208                 strcpy(result, items[(scroll + choice) * 2]);
209             rval = 0;
210             key = ESC;  /* Punt! */
211             break;
212         }
213
214         /* Shortcut to cancel? */
215         if (toupper(key) == cancelButton) {
216             if (ditems && result && ditems[CANCEL_BUTTON].fire) {
217                 int status;
218                 WINDOW *save;
219
220                 save = dupwin(newscr);
221                 status = ditems[CANCEL_BUTTON].fire(&ditems[CANCEL_BUTTON]);
222                 if (status & DITEM_RESTORE) {
223                     touchwin(save);
224                     wrefresh(save);
225                 }
226                 delwin(save);
227             }
228             rval = 1;
229             key = ESC;  /* Run away! */
230             break;
231         }
232
233         /* Check if key pressed matches first character of any item tag in menu */
234         for (i = 0; i < max_choice; i++)        
235             if (key < 0x100 && key != ' ' && toupper(key) == toupper(items[(scroll + i) * 2][0]))
236                 break;
237
238         if (i < max_choice || (key >= '1' && key <= MIN('9', '0'+max_choice)) || KEY_IS_UP(key) || KEY_IS_DOWN(key)) {
239             if (key >= '1' && key <= MIN('9', '0'+max_choice))
240                 i = key - '1';
241             else if (KEY_IS_UP(key)) {
242                 if (!choice) {
243                     if (scroll) {
244                         /* Scroll menu down */
245                         getyx(dialog, cur_y, cur_x);    /* Save cursor position */
246                         if (menu_height > 1) {
247                             /* De-highlight current first item before scrolling down */
248                             print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, FALSE, DREF(ditems, scroll), menu_width, tag_x, item_x);
249                             scrollok(menu, TRUE);
250                             wscrl(menu, -1);
251                             scrollok(menu, FALSE);
252                         }
253                         scroll--;
254                         print_item(menu, items[scroll * 2], items[scroll * 2 + 1], 0, TRUE, DREF(ditems, scroll), menu_width, tag_x, item_x);
255                         wnoutrefresh(menu);
256                         print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
257                         wrefresh(dialog);
258                     }
259                     continue;    /* wait for another key press */
260                 }
261                 else
262                     i = choice - 1;
263             }
264             else if (KEY_IS_DOWN(key)) {
265                 if (choice == max_choice - 1) {
266                     if (scroll + choice < item_no - 1) {
267                         /* Scroll menu up */
268                         getyx(dialog, cur_y, cur_x);    /* Save cursor position */
269                         if (menu_height > 1) {
270                             /* De-highlight current last item before scrolling up */
271                             print_item(menu, items[(scroll + max_choice - 1) * 2],
272                                        items[(scroll + max_choice - 1) * 2 + 1],
273                                        max_choice-1, FALSE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x);
274                             scrollok(menu, TRUE);
275                             scroll(menu);
276                             scrollok(menu, FALSE);
277                         }
278                         scroll++;
279                         print_item(menu, items[(scroll + max_choice - 1) * 2],
280                                    items[(scroll + max_choice - 1) * 2 + 1],
281                                    max_choice - 1, TRUE, DREF(ditems, scroll + max_choice - 1), menu_width, tag_x, item_x);
282                         wnoutrefresh(menu);
283                         print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
284                         wrefresh(dialog);
285                     }
286                     continue;    /* wait for another key press */
287                 }
288                 else
289                     i = choice + 1;
290             }
291             
292             if (i != choice) {
293                 /* De-highlight current item */
294                 getyx(dialog, cur_y, cur_x);    /* Save cursor position */
295                 print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, FALSE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x);
296                 
297                 /* Highlight new item */
298                 choice = i;
299                 print_item(menu, items[(scroll + choice) * 2], items[(scroll + choice) * 2 + 1], choice, TRUE, DREF(ditems, scroll + choice), menu_width, tag_x, item_x);
300                 wnoutrefresh(menu);
301                 wmove(dialog, cur_y, cur_x);  /* Restore cursor to previous position */
302                 wrefresh(dialog);
303             }
304             continue;    /* wait for another key press */
305         }
306         
307         switch (key) {
308         case KEY_PPAGE:
309             if (scroll > height - 4) {  /* can we go up? */
310                 scroll -= (height - 4);
311             } else {
312                 scroll = 0;
313             }
314             redraw_menu = TRUE;
315             break;
316             
317         case KEY_NPAGE:
318             if (scroll + menu_height >= item_no-1 - menu_height) { /* can we go down a full page? */
319                 scroll = item_no - menu_height;
320                 if (scroll < 0)
321                     scroll = 0;
322             } else {
323                 scroll += menu_height;
324             }
325             redraw_menu = TRUE;
326             break;
327             
328         case KEY_HOME:
329             scroll = 0;
330             choice = 0;
331             redraw_menu = TRUE;
332             break;
333             
334         case KEY_END:
335             scroll = item_no - menu_height;
336             if (scroll < 0)
337                 scroll = 0;
338             choice = max_choice - 1;
339             redraw_menu = TRUE;
340             break;
341             
342         case KEY_BTAB:
343         case TAB:
344         case KEY_LEFT:
345         case KEY_RIGHT:
346             button = !button;
347             if (ditems && result) {
348                 print_button(dialog, ditems[CANCEL_BUTTON].prompt, y, x + strlen(ditems[OK_BUTTON].prompt) + 5, ditems[CANCEL_BUTTON].checked ? ditems[CANCEL_BUTTON].checked(&ditems[CANCEL_BUTTON]) : button);
349                 print_button(dialog, ditems[OK_BUTTON].prompt, y, x, ditems[OK_BUTTON].checked ? ditems[OK_BUTTON].checked(&ditems[OK_BUTTON]) : !button);
350                 ok_space = 1;
351                 cancel_space = strlen(ditems[OK_BUTTON].prompt) + 6;
352             }
353             else {
354                 print_button(dialog, "Cancel", y, x + 14, button);
355                 print_button(dialog, "  OK  ", y, x, !button);
356                 ok_space = 3;
357                 cancel_space = 15;
358             }
359             if (button)
360                 wmove(dialog, y, x+cancel_space);
361             else
362                 wmove(dialog, y, x+ok_space);
363             wrefresh(dialog);
364             break;
365             
366         case ' ':
367         case '\r':
368         case '\n':
369             if (!button) {
370                 /* A fire routine can do just about anything to the screen, so be prepared
371                    to accept some hints as to what to do in the aftermath. */
372                 if (ditems) {
373                     if (ditems[scroll + choice].fire) {
374                         int status;
375                         WINDOW *save;
376
377                         save = dupwin(newscr);
378                         status = ditems[scroll + choice].fire(&ditems[scroll + choice]);
379                         if (status & DITEM_RESTORE) {
380                             touchwin(save);
381                             wrefresh(save);
382                         }
383                         delwin(save);
384                         if (status & DITEM_CONTINUE)
385                             continue;
386                         else if (status & DITEM_LEAVE_MENU) {
387                             /* Allow a fire action to take us out of the menu */
388                             key = ESC;
389                             break;
390                         }
391                         else if (status & DITEM_RECREATE) {
392                             delwin(menu);
393                             delwin(dialog);
394                             dialog_clear();
395                             goto draw;
396                         }
397                     }
398                 }
399                 else if (result)
400                     strcpy(result, items[(scroll+choice)*2]);
401             }
402             rval = button;
403             key = ESC;
404             break;
405             
406         case ESC:
407             rval = -1;
408             break;
409             
410         case KEY_F(1):
411         case '?':
412             display_helpfile();
413             break;
414         }
415         
416         /* save info about menu item position */
417         if (ch)
418             *ch = choice;
419         if (sc)
420             *sc = scroll;
421         
422         if (redraw_menu) {
423             for (i = 0; i < max_choice; i++) {
424                 print_item(menu, items[(scroll + i) * 2], items[(scroll + i) * 2 + 1], i, i == choice, DREF(ditems, scroll + i), menu_width, tag_x, item_x);
425             }
426             wnoutrefresh(menu);
427             getyx(dialog, cur_y, cur_x);    /* Save cursor position */
428             print_arrows(dialog, scroll, menu_height, item_no, box_x, box_y, tag_x, cur_x, cur_y);
429             wmove(dialog, cur_y, cur_x);  /* Restore cursor to previous position */
430             wrefresh(dialog);
431             redraw_menu = FALSE;
432         }
433     }
434     delwin(menu);
435     delwin(dialog);
436     return rval;
437 }
438
439
440 /*
441  * Print menu item
442  */
443 static void
444 print_item(WINDOW *win, unsigned char *tag, unsigned char *item, int choice, int selected, dialogMenuItem *me, int menu_width, int tag_x, int item_x)
445 {
446     int i;
447     
448     /* Clear 'residue' of last item */
449     wattrset(win, menubox_attr);
450     wmove(win, choice, 0);
451     for (i = 0; i < menu_width; i++)
452         waddch(win, ' ');
453     wmove(win, choice, tag_x);
454     wattrset(win, selected ? tag_key_selected_attr : tag_key_attr);
455     waddch(win, tag[0]);
456     wattrset(win, selected ? tag_selected_attr : tag_attr);
457     waddnstr(win, tag + 1, item_x - tag_x - 3);
458     wmove(win, choice, item_x);
459     wattrset(win, selected ? item_selected_attr : item_attr);
460     waddnstr(win, item, menu_width - item_x - 1);
461     /* If have a selection handler for this, call it */
462     if (me && me->selected) {
463         wrefresh(win);
464         me->selected(me, selected);
465     }
466 }
467 /* End of print_item() */