ncurses 6.0 - patch 20171125
[ncurses.git] / test / demo_menus.c
1 /****************************************************************************
2  * Copyright (c) 2005-2016,2017 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 /*
29  * $Id: demo_menus.c,v 1.65 2017/11/24 21:03:30 tom Exp $
30  *
31  * Demonstrate a variety of functions from the menu library.
32  * Thomas Dickey - 2005/4/9
33  */
34 /*
35 item_description                -
36 item_init                       -
37 item_opts                       -
38 item_opts_off                   -
39 item_opts_on                    -
40 item_term                       -
41 item_visible                    -
42 menu_back                       -
43 menu_fore                       -
44 menu_format                     -
45 menu_grey                       -
46 menu_init                       -
47 menu_opts                       -
48 menu_pad                        -
49 menu_request_by_name            -
50 menu_request_name               -
51 menu_term                       -
52 menu_userptr                    -
53 set_current_item                -
54 set_item_opts                   -
55 set_menu_grey                   -
56 set_menu_items                  -
57 set_menu_opts                   -
58 set_menu_pad                    -
59 set_menu_pattern                -
60 set_menu_spacing                -
61 set_menu_userptr                -
62 set_top_row                     -
63 top_row                         -
64 */
65
66 #include <test.priv.h>
67
68 #if USE_LIBMENU
69
70 #include <menu.h>
71
72 #include <sys/types.h>
73 #include <sys/stat.h>
74
75 #ifdef NCURSES_VERSION
76 #ifdef TRACE
77 static unsigned save_trace = TRACE_ORDINARY | TRACE_CALLS;
78 static MENU *mpTrace;
79 #endif
80 #endif
81
82 typedef enum {
83     eBanner = -1
84     ,eFile
85     ,eSelect
86 #ifdef TRACE
87     ,eTrace
88 #endif
89     ,eMAX
90 } MenuNo;
91
92 #define okMenuNo(n) (((n) > eBanner) && ((n) < eMAX))
93
94 #define MENU_Y  1
95
96 typedef struct {
97     NCURSES_CONST char *name;
98     void (*func) (int);
99     unsigned mask;
100 } MENU_DATA;
101
102 static void call_files(int);
103
104 static MENU *mpBanner;
105 static MENU *mpFile;
106 static MENU *mpSelect;
107
108 static WINDOW *status;
109
110 static bool loaded_file = FALSE;
111
112 static char empty[1];
113 static void failed(const char *s) GCC_NORETURN;
114
115 static void
116 failed(const char *s)
117 {
118     perror(s);
119     endwin();
120     ExitProgram(EXIT_FAILURE);
121 }
122
123 /* Common function to allow ^T to toggle trace-mode in the middle of a test
124  * so that trace-files can be made smaller.
125  */
126 static int
127 wGetchar(WINDOW *win)
128 {
129     int c;
130 #ifdef TRACE
131     while ((c = wgetch(win)) == CTRL('T')) {
132         if (_nc_tracing) {
133             save_trace = _nc_tracing;
134             Trace(("TOGGLE-TRACING OFF"));
135             _nc_tracing = 0;
136         } else {
137             _nc_tracing = save_trace;
138         }
139         trace(_nc_tracing);
140         if (_nc_tracing)
141             Trace(("TOGGLE-TRACING ON"));
142     }
143 #else
144     c = wgetch(win);
145 #endif
146     return c;
147 }
148 #define Getchar() wGetchar(stdscr)
149
150 static int
151 menu_virtualize(int c)
152 {
153     int result;
154
155     if (c == '\n' || c == KEY_EXIT)
156         result = (MAX_COMMAND + 1);
157     else if (c == 'u')
158         result = (REQ_SCR_ULINE);
159     else if (c == 'd')
160         result = (REQ_SCR_DLINE);
161     else if (c == 'b' || c == KEY_NPAGE)
162         result = (REQ_SCR_UPAGE);
163     else if (c == 'f' || c == KEY_PPAGE)
164         result = (REQ_SCR_DPAGE);
165     else if (c == 'l' || c == KEY_LEFT || c == KEY_BTAB)
166         result = (REQ_LEFT_ITEM);
167     else if (c == 'n' || c == KEY_DOWN)
168         result = (REQ_NEXT_ITEM);
169     else if (c == 'p' || c == KEY_UP)
170         result = (REQ_PREV_ITEM);
171     else if (c == 'r' || c == KEY_RIGHT || c == '\t')
172         result = (REQ_RIGHT_ITEM);
173     else if (c == ' ')
174         result = (REQ_TOGGLE_ITEM);
175     else {
176         if (c != KEY_MOUSE)
177             beep();
178         result = (c);
179     }
180     return result;
181 }
182
183 static int
184 menu_getc(MENU * m)
185 {
186     return wGetchar(menu_win(m));
187 }
188
189 static int
190 menu_offset(MenuNo number)
191 {
192     int result = 0;
193
194     if (okMenuNo(number)) {
195         int spc_rows;
196 #ifdef NCURSES_VERSION
197         int spc_desc, spc_cols;
198         menu_spacing(mpBanner, &spc_desc, &spc_rows, &spc_cols);
199 #else
200         spc_rows = 0;
201 #endif
202
203         /* FIXME: MENU.itemlen seems the only way to get actual width of items */
204         result = (number - (eBanner + 1)) * (menu_itemwidth(mpBanner) + spc_rows);
205     }
206     return result;
207 }
208
209 static void
210 my_menu_init(MENU * menu)
211 {
212     Trace(("called MenuHook my_menu_init"));
213     mvwprintw(status, 2, 0, "menu_init %p", (void *) menu);
214     wclrtoeol(status);
215     wrefresh(status);
216 }
217
218 static void
219 my_menu_term(MENU * menu)
220 {
221     Trace(("called MenuHook my_menu_term"));
222     mvwprintw(status, 2, 0, "menu_term %p", (void *) menu);
223     wclrtoeol(status);
224     wrefresh(status);
225 }
226
227 static void
228 my_item_init(MENU * menu)
229 {
230     ITEM *item = current_item(menu);
231     const char *name = item_name(item);
232
233     Trace(("called MenuHook my_item_init (%s)", name));
234     mvwprintw(status, 2, 0, "item_init %s", name);
235     wclrtoeol(status);
236     wrefresh(status);
237 }
238
239 static void
240 my_item_term(MENU * menu)
241 {
242     ITEM *item = current_item(menu);
243     const char *name = item_name(item);
244
245     Trace(("called MenuHook my_item_term (%s)", name));
246     mvwprintw(status, 2, 0, "item_term %s", name);
247     wclrtoeol(status);
248     wrefresh(status);
249 }
250
251 static MENU *
252 menu_create(ITEM ** items, int count, int ncols, MenuNo number)
253 {
254     MENU *result;
255     WINDOW *menuwin;
256     int mrows, mcols;
257     int y = okMenuNo(number) ? MENU_Y : 0;
258     int x = menu_offset(number);
259     int margin = (y == MENU_Y) ? 1 : 0;
260     int maxcol = (ncols + x) < COLS ? ncols : (COLS - x - 1);
261     int maxrow = (count + 1) / ncols;
262
263     if ((maxrow + y) >= (LINES - 4))
264         maxrow = LINES - 4 - y;
265
266     result = new_menu(items);
267
268     if (has_colors()) {
269         set_menu_fore(result, (chtype) COLOR_PAIR(1));
270         set_menu_back(result, (chtype) COLOR_PAIR(2));
271     }
272
273     set_menu_format(result, maxrow, maxcol);
274     scale_menu(result, &mrows, &mcols);
275
276     if (mcols + (2 * margin + x) >= COLS)
277         mcols = COLS - (2 * margin + x);
278
279     menuwin = newwin(mrows + (2 * margin), mcols + (2 * margin), y, x);
280     set_menu_win(result, menuwin);
281     keypad(menuwin, TRUE);
282     if (margin)
283         box(menuwin, 0, 0);
284
285     set_menu_sub(result, derwin(menuwin, mrows, mcols, margin, margin));
286
287 #ifdef TRACE
288     if (number == eTrace)
289         menu_opts_off(result, O_ONEVALUE);
290     else
291         menu_opts_on(result, O_ONEVALUE);
292 #endif
293 #if defined(NCURSES_MOUSE_VERSION) && defined(O_MOUSE_MENU)
294     menu_opts_on(result, O_MOUSE_MENU);
295 #endif
296
297     post_menu(result);
298
299     set_menu_init(result, my_menu_init);
300     set_menu_term(result, my_menu_term);
301     set_item_init(result, my_item_init);
302     set_item_term(result, my_item_term);
303     return result;
304 }
305
306 static void
307 menu_destroy(MENU * m)
308 {
309     int count;
310
311     Trace(("menu_destroy %p", (void *) m));
312     if (m != 0) {
313         ITEM **items = menu_items(m);
314         const char *blob = 0;
315
316         count = item_count(m);
317         Trace(("menu_destroy %p count %d", (void *) m, count));
318         if ((count > 0) && (m == mpSelect)) {
319             blob = item_name(*items);
320         }
321
322         unpost_menu(m);
323         free_menu(m);
324
325         /* free the extra data allocated in build_select_menu() */
326         if ((count > 0) && (m == mpSelect)) {
327             if (blob && loaded_file) {
328                 Trace(("freeing blob %p", blob));
329                 free((void *) blob);
330             }
331             free(items);
332             items = 0;
333         }
334 #ifdef TRACE
335         if ((count > 0) && (m == mpTrace)) {
336             ITEM **ip = items;
337             if (ip != 0) {
338                 while (*ip)
339                     free(*ip++);
340             }
341         }
342 #endif
343     }
344 }
345
346 /* force the given menu to appear */
347 static void
348 menu_display(MENU * m)
349 {
350     touchwin(menu_win(m));
351     wrefresh(menu_win(m));
352 }
353
354 /*****************************************************************************/
355
356 static void
357 build_file_menu(MenuNo number)
358 {
359     static MENU_DATA table[] =
360     {
361         {"Exit", call_files, 0},
362         {(char *) 0, 0, 0}
363     };
364     static ITEM *items[SIZEOF(table)];
365
366     ITEM **ip = items;
367     int n;
368
369     for (n = 0; table[n].name != 0; ++n) {
370         *ip = new_item(table[n].name, empty);
371         set_item_userptr(*ip, (void *) &table[n]);
372         ++ip;
373     }
374     *ip = (ITEM *) 0;
375
376     mpFile = menu_create(items, SIZEOF(table) - 1, 1, number);
377 }
378
379 static int
380 perform_file_menu(int cmd)
381 {
382     return menu_driver(mpFile, cmd);
383 }
384
385 /*****************************************************************************/
386
387 static void
388 call_select(int code)
389 {
390     (void) code;
391     Trace(("Selected item %d", code));
392 }
393
394 static void
395 build_select_menu(MenuNo number, char *filename)
396 {
397 #define MY_DATA(name) { name, call_select, 0 }
398     static MENU_DATA table[] =
399     {
400         MY_DATA("Lions"),
401         MY_DATA("Tigers"),
402         MY_DATA("Bears"),
403         MY_DATA("(Oh my!)"),
404         MY_DATA("Newts"),
405         MY_DATA("Platypi"),
406         MY_DATA("Lemurs"),
407         MY_DATA("(Oh really?!)"),
408         MY_DATA("Leopards"),
409         MY_DATA("Panthers"),
410         MY_DATA("Pumas"),
411         MY_DATA("Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs"),
412         MY_DATA("Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs, Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs"),
413         {(char *) 0, 0, 0}
414     };
415     static ITEM **items;
416
417     ITEM **ip;
418     MENU_DATA *ap = 0;
419     MENU_DATA *myList = 0;
420     int i;
421     size_t count = 0;
422
423     if (filename != 0) {
424         struct stat sb;
425         if (stat(filename, &sb) == 0
426             && (sb.st_mode & S_IFMT) == S_IFREG
427             && sb.st_size != 0) {
428             size_t size = (size_t) sb.st_size;
429             unsigned j, k;
430             char *blob = typeMalloc(char, size + 1);
431             MENU_DATA *list = typeCalloc(MENU_DATA, size + 1);
432
433             items = typeCalloc(ITEM *, size + 1);
434             Trace(("build_select_menu blob=%p, items=%p",
435                    (void *) blob,
436                    (void *) items));
437             if (blob != 0 && list != 0) {
438                 FILE *fp = fopen(filename, "r");
439                 if (fp != 0) {
440                     if (fread(blob, sizeof(char), size, fp) == size) {
441                         bool mark = TRUE;
442                         for (j = k = 0; j < size; ++j) {
443                             if (mark) {
444                                 list[k++].name = blob + j;
445                                 mark = FALSE;
446                             }
447                             if (blob[j] == '\n') {
448                                 blob[j] = '\0';
449                                 if (k > 0 && *list[k - 1].name == '\0')
450                                     --k;
451                                 mark = TRUE;
452                             } else if (blob[j] == '\t') {
453                                 blob[j] = ' ';  /* menu items are printable */
454                             }
455                         }
456                         list[k].name = 0;
457                         count = k;
458                         ap = myList = list;
459                     }
460                     fclose(fp);
461                 }
462                 loaded_file = TRUE;
463             }
464             if (ap == 0)
465                 free(items);
466         }
467     }
468     if (ap == 0) {
469         count = SIZEOF(table) - 1;
470         items = typeCalloc(ITEM *, count + 1);
471         ap = table;
472     }
473
474     ip = items;
475     for (i = 0; ap[i].name != 0; ++i) {
476         ap[i].func = call_select;
477         ap[i].mask = (unsigned) i;
478         *ip = new_item(ap[i].name, empty);
479         set_item_userptr(*ip, (void *) &table[i]);
480         ++ip;
481     }
482     *ip = 0;
483
484     mpSelect = menu_create(items, (int) count, 1, number);
485     if (myList != 0)
486         free(myList);
487 }
488
489 static int
490 perform_select_menu(int cmd)
491 {
492     return menu_driver(mpSelect, cmd);
493 }
494
495 /*****************************************************************************/
496
497 #ifdef TRACE
498
499 static void
500 call_trace(int code)
501 {
502     (void) code;
503     Trace(("Updating trace mask %d", code));
504 }
505
506 #define T_TBL(name) { #name, call_trace, name }
507 static MENU_DATA t_tbl[] =
508 {
509
510     T_TBL(TRACE_DISABLE),
511     T_TBL(TRACE_TIMES),
512     T_TBL(TRACE_TPUTS),
513     T_TBL(TRACE_UPDATE),
514     T_TBL(TRACE_MOVE),
515     T_TBL(TRACE_CHARPUT),
516     T_TBL(TRACE_ORDINARY),
517     T_TBL(TRACE_CALLS),
518     T_TBL(TRACE_VIRTPUT),
519     T_TBL(TRACE_IEVENT),
520     T_TBL(TRACE_BITS),
521     T_TBL(TRACE_ICALLS),
522     T_TBL(TRACE_CCALLS),
523     T_TBL(TRACE_DATABASE),
524     T_TBL(TRACE_ATTRS),
525     T_TBL(TRACE_MAXIMUM),
526     {
527         (char *) 0, 0, 0
528     }
529 };
530
531 static void
532 build_trace_menu(MenuNo number)
533 {
534     static ITEM *items[SIZEOF(t_tbl)];
535
536     ITEM **ip = items;
537     int n;
538
539     for (n = 0; t_tbl[n].name != 0; n++) {
540         *ip = new_item(t_tbl[n].name, empty);
541         set_item_userptr(*ip, (void *) &t_tbl[n]);
542         ++ip;
543     }
544     *ip = (ITEM *) 0;
545
546     mpTrace = menu_create(items, SIZEOF(t_tbl) - 1, 2, number);
547 }
548
549 static char *
550 tracetrace(unsigned tlevel)
551 {
552     static char *buf;
553     static size_t need = 12;
554     int n;
555
556     if (buf == 0) {
557         for (n = 0; t_tbl[n].name != 0; n++)
558             need += strlen(t_tbl[n].name) + 2;
559         buf = typeMalloc(char, need);
560         if (!buf)
561             failed("tracetrace");
562     }
563     _nc_SPRINTF(buf, _nc_SLIMIT(need) "0x%02x = {", tlevel);
564     if (tlevel == 0) {
565         _nc_STRCAT(buf, t_tbl[0].name, need);
566         _nc_STRCAT(buf, ", ", need);
567     } else {
568         for (n = 1; t_tbl[n].name != 0; n++)
569             if ((tlevel & t_tbl[n].mask) == t_tbl[n].mask) {
570                 _nc_STRCAT(buf, t_tbl[n].name, need);
571                 _nc_STRCAT(buf, ", ", need);
572             }
573     }
574     if (buf[strlen(buf) - 2] == ',')
575         buf[strlen(buf) - 2] = '\0';
576     _nc_STRCAT(buf, "}", need);
577     return buf;
578 }
579
580 /* fake a dynamically reconfigurable menu using the 0th entry to deselect
581  * the others
582  */
583 static bool
584 update_trace_menu(MENU * m)
585 {
586     ITEM **items;
587     ITEM *i, **p;
588     bool changed = FALSE;
589
590     items = menu_items(m);
591     i = current_item(m);
592     if (i == items[0]) {
593         if (item_value(i)) {
594             for (p = items + 1; *p != 0; p++)
595                 if (item_value(*p)) {
596                     set_item_value(*p, FALSE);
597                     changed = TRUE;
598                 }
599         }
600     }
601     return changed;
602 }
603
604 static int
605 perform_trace_menu(int cmd)
606 /* interactively set the trace level */
607 {
608     ITEM **ip;
609     unsigned newtrace;
610     int result;
611
612     for (ip = menu_items(mpTrace); *ip; ip++) {
613         MENU_DATA *td = (MENU_DATA *) item_userptr(*ip);
614         unsigned mask = td->mask;
615         if (mask == 0)
616             set_item_value(*ip, _nc_tracing == 0);
617         else if ((mask & _nc_tracing) == mask)
618             set_item_value(*ip, TRUE);
619     }
620
621     result = menu_driver(mpTrace, cmd);
622
623     if (result == E_OK) {
624         if (update_trace_menu(mpTrace) || cmd == REQ_TOGGLE_ITEM) {
625             newtrace = 0;
626             for (ip = menu_items(mpTrace); *ip; ip++) {
627                 if (item_value(*ip)) {
628                     MENU_DATA *td = (MENU_DATA *) item_userptr(*ip);
629                     newtrace |= td->mask;
630                 }
631             }
632             trace(newtrace);
633             Trace(("trace level interactively set to %s", tracetrace(_nc_tracing)));
634
635             MvWPrintw(status, 1, 0,
636                       "Trace level is %s\n", tracetrace(_nc_tracing));
637             wrefresh(status);
638         }
639     }
640     return result;
641 }
642 #endif /* TRACE */
643
644 /*****************************************************************************/
645
646 static int
647 menu_number(void)
648 {
649     return item_index(current_item(mpBanner)) - (eBanner + 1);
650 }
651
652 static MENU *
653 current_menu(void)
654 {
655     MENU *result;
656
657     switch (menu_number()) {
658     case eFile:
659         result = mpFile;
660         break;
661     case eSelect:
662         result = mpSelect;
663         break;
664 #ifdef TRACE
665     case eTrace:
666         result = mpTrace;
667         break;
668 #endif
669     default:
670         result = 0;
671         break;
672     }
673     return result;
674 }
675
676 static void
677 call_menus(int code)
678 {
679     (void) code;
680     Trace(("Activated menu %d\n", code));
681 }
682
683 static void
684 build_menus(char *filename)
685 {
686     static MENU_DATA table[] =
687     {
688         {"File", call_menus, 0},
689         {"Select", call_menus, 1},
690 #ifdef TRACE
691         {"Trace", call_menus, 2},
692 #endif
693         {(char *) 0, 0, 0}
694     };
695     static ITEM *items[SIZEOF(table)];
696
697     ITEM **ip = items;
698     int n;
699
700     for (n = 0; table[n].name != 0; ++n) {
701         *ip = new_item(table[n].name, empty);
702         set_item_userptr(*ip, (void *) &table[n]);
703         ++ip;
704     }
705     *ip = (ITEM *) 0;
706
707     mpBanner = menu_create(items, SIZEOF(table) - 1, SIZEOF(table) - 1, eBanner);
708     set_menu_mark(mpBanner, ">");
709
710     build_file_menu(eFile);
711     build_select_menu(eSelect, filename);
712 #ifdef TRACE
713     build_trace_menu(eTrace);
714 #endif
715 }
716
717 static int
718 move_menu(MENU * menu, MENU * current, int by_y, int by_x)
719 {
720     WINDOW *top_win = menu_win(menu);
721     WINDOW *sub_win = menu_sub(menu);
722     int y0, x0;
723     int y1, x1;
724     int result;
725
726     getbegyx(top_win, y0, x0);
727     y0 += by_y;
728     x0 += by_x;
729
730     getbegyx(sub_win, y1, x1);
731     y1 += by_y;
732     x1 += by_x;
733
734     if ((result = mvwin(top_win, y0, x0)) != ERR) {
735 #if defined(NCURSES_VERSION_PATCH) && (NCURSES_VERSION_PATCH < 20060218)
736         sub_win->_begy = y1;
737         sub_win->_begx = x1;
738 #else
739         mvwin(sub_win, y1, x1);
740 #endif
741         if (menu == current) {
742             touchwin(top_win);
743             wnoutrefresh(top_win);
744         }
745     }
746     return result;
747 }
748
749 /*
750  * Move the menus around on the screen, to test mvwin().
751  */
752 static void
753 move_menus(MENU * current, int by_y, int by_x)
754 {
755     if (move_menu(mpBanner, current, by_y, by_x) != ERR) {
756         erase();
757         wnoutrefresh(stdscr);
758         move_menu(mpFile, current, by_y, by_x);
759         move_menu(mpSelect, current, by_y, by_x);
760 #ifdef TRACE
761         move_menu(mpTrace, current, by_y, by_x);
762 #endif
763         doupdate();
764     }
765 }
766
767 #ifdef KEY_RESIZE
768 static void
769 resize_menu(MENU ** menu)
770 {
771 #if 0
772     WINDOW *win = menu_win(*menu);
773     WINDOW *sub = menu_sub(*menu);
774 #endif
775     (void) menu;
776 }
777
778 static void
779 resize_menus(MENU * current)
780 {
781     (void) current;
782
783     werase(status);
784     wnoutrefresh(status);
785     wresize(status, 1, COLS);
786     mvwin(status, LINES - 1, 0);
787
788     resize_menu(&mpBanner);
789     resize_menu(&mpFile);
790     resize_menu(&mpSelect);
791 #ifdef TRACE
792     resize_menu(&mpTrace);
793 #endif
794 }
795 #endif
796
797 static void
798 show_status(int ch, MENU * menu)
799 {
800     wmove(status, 0, 0);
801     wprintw(status, "key %s, menu %d, mark %s, match %s",
802             keyname(ch),
803             menu_number(),
804             menu_mark(menu),
805             menu_pattern(menu));
806     wclrtoeol(status);
807     wrefresh(status);
808 }
809
810 static void
811 perform_menus(void)
812 {
813     MENU *this_menu;
814     MENU *last_menu = mpFile;
815     int code = E_UNKNOWN_COMMAND;
816     int cmd;
817     int ch = ERR;
818
819 #ifdef NCURSES_MOUSE_VERSION
820     mousemask(BUTTON1_CLICKED, (mmask_t *) 0);
821 #endif
822
823     menu_display(last_menu);
824
825     for (;;) {
826
827         if (ch != ERR)
828             show_status(ch, last_menu);
829
830         ch = menu_getc(mpBanner);
831
832         /*
833          * Provide for moving the menu around in the screen using shifted
834          * cursor keys.
835          */
836         switch (ch) {
837         case KEY_SF:
838             move_menus(last_menu, 1, 0);
839             continue;
840         case KEY_SR:
841             move_menus(last_menu, -1, 0);
842             continue;
843         case KEY_SLEFT:
844             move_menus(last_menu, 0, -1);
845             continue;
846         case KEY_SRIGHT:
847             move_menus(last_menu, 0, 1);
848             continue;
849 #ifdef KEY_RESIZE
850         case KEY_RESIZE:
851             resize_menus(last_menu);
852             continue;
853 #endif
854         }
855         cmd = menu_virtualize(ch);
856
857         switch (cmd) {
858             /*
859              * The banner menu acts solely to select one of the other menus.
860              * Move between its items, wrapping at the left/right limits.
861              */
862         case REQ_LEFT_ITEM:
863         case REQ_RIGHT_ITEM:
864             code = menu_driver(mpBanner, cmd);
865             if (code == E_REQUEST_DENIED) {
866                 if (menu_number() > 0)
867                     code = menu_driver(mpBanner, REQ_FIRST_ITEM);
868                 else
869                     code = menu_driver(mpBanner, REQ_LAST_ITEM);
870             }
871             break;
872         default:
873             switch (menu_number()) {
874             case eFile:
875                 code = perform_file_menu(cmd);
876                 break;
877             case eSelect:
878                 code = perform_select_menu(cmd);
879                 break;
880 #ifdef TRACE
881             case eTrace:
882                 code = perform_trace_menu(cmd);
883                 break;
884 #endif
885             }
886
887 #if defined(NCURSES_MOUSE_VERSION) && defined(O_MOUSE_MENU)
888             if ((code == E_REQUEST_DENIED) && (cmd == KEY_MOUSE)) {
889                 (void) menu_getc(mpBanner);
890                 code = menu_driver(mpBanner, cmd);
891                 if (code == E_REQUEST_DENIED) {
892                     MEVENT event;
893                     if (menu_getc(mpBanner) == KEY_MOUSE)
894                         getmouse(&event);       /* give up */
895                 }
896             }
897 #endif
898
899             break;
900         }
901
902         if (code == E_OK) {
903             this_menu = current_menu();
904             if (this_menu != last_menu) {
905                 move(1, 0);
906                 clrtobot();
907                 box(menu_win(this_menu), 0, 0);
908                 refresh();
909
910                 /* force the current menu to appear */
911                 menu_display(this_menu);
912
913                 last_menu = this_menu;
914             }
915         }
916         wrefresh(menu_win(last_menu));
917         if (code == E_UNKNOWN_COMMAND
918             || code == E_NOT_POSTED) {
919             ITEM *item = current_item(last_menu);
920             MENU_DATA *td = (MENU_DATA *) item_userptr(item);
921             td->func((int) td->mask);
922         }
923         if (code == E_REQUEST_DENIED)
924             beep();
925         continue;
926     }
927 }
928
929 static void
930 destroy_menus(void)
931 {
932     menu_destroy(mpFile);
933     menu_destroy(mpSelect);
934 #ifdef TRACE
935     menu_destroy(mpTrace);
936 #endif
937     menu_destroy(mpBanner);
938 }
939
940 #if HAVE_RIPOFFLINE
941 static int
942 rip_footer(WINDOW *win, int cols)
943 {
944     wbkgd(win, A_REVERSE);
945     werase(win);
946     wmove(win, 0, 0);
947     wprintw(win, "footer: %d columns", cols);
948     wnoutrefresh(win);
949     return OK;
950 }
951
952 static int
953 rip_header(WINDOW *win, int cols)
954 {
955     wbkgd(win, A_REVERSE);
956     werase(win);
957     wmove(win, 0, 0);
958     wprintw(win, "header: %d columns", cols);
959     wnoutrefresh(win);
960     return OK;
961 }
962 #endif /* HAVE_RIPOFFLINE */
963
964 static void
965 call_files(int code)
966 {
967     switch (code) {
968     case 0:
969         destroy_menus();
970         endwin();
971         printf("DONE!\n");
972         ExitProgram(EXIT_SUCCESS);
973     }
974 }
975
976 static void
977 usage(void)
978 {
979     static const char *const tbl[] =
980     {
981         "Usage: demo_menus [options] [menu-file]"
982         ,""
983         ,"Options:"
984 #if HAVE_RIPOFFLINE
985         ,"  -f       rip-off footer line (can repeat)"
986         ,"  -h       rip-off header line (can repeat)"
987 #endif
988 #ifdef TRACE
989         ,"  -t mask  specify default trace-level (may toggle with ^T)"
990 #endif
991     };
992     size_t n;
993     for (n = 0; n < SIZEOF(tbl); n++)
994         fprintf(stderr, "%s\n", tbl[n]);
995     ExitProgram(EXIT_FAILURE);
996 }
997
998 int
999 main(int argc, char *argv[])
1000 {
1001     int c;
1002
1003     setlocale(LC_ALL, "");
1004
1005     while ((c = getopt(argc, argv, "fht:")) != -1) {
1006         switch (c) {
1007 #if HAVE_RIPOFFLINE
1008         case 'f':
1009             ripoffline(-1, rip_footer);
1010             break;
1011         case 'h':
1012             ripoffline(1, rip_header);
1013             break;
1014 #endif /* HAVE_RIPOFFLINE */
1015 #ifdef TRACE
1016         case 't':
1017             trace((unsigned) strtoul(optarg, 0, 0));
1018             break;
1019 #endif
1020         default:
1021             usage();
1022         }
1023     }
1024
1025     initscr();
1026     noraw();
1027     cbreak();
1028     noecho();
1029
1030     if (has_colors()) {
1031         start_color();
1032         init_pair(1, COLOR_RED, COLOR_BLACK);
1033         init_pair(2, COLOR_BLUE, COLOR_WHITE);
1034     }
1035     status = newwin(3, COLS, LINES - 3, 0);
1036     build_menus(argc > 1 ? argv[1] : 0);
1037     perform_menus();
1038     destroy_menus();
1039
1040     endwin();
1041     ExitProgram(EXIT_SUCCESS);
1042 }
1043 #else
1044 int
1045 main(void)
1046 {
1047     printf("This program requires the curses menu library\n");
1048     ExitProgram(EXIT_FAILURE);
1049 }
1050 #endif