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