1/*
2 * $Id: demo_menus.c,v 1.13 2005/10/01 15:54:31 tom Exp $
3 *
4 * Demonstrate a variety of functions from the menu library.
5 * Thomas Dickey - 2005/4/9
6 */
7/*
8item_description		-
9item_init			-
10item_opts			-
11item_opts_off			-
12item_opts_on			-
13item_term			-
14item_userptr			-
15item_visible			-
16menu_back			-
17menu_fore			-
18menu_format			-
19menu_grey			-
20menu_init			-
21menu_mark			-
22menu_opts			-
23menu_pad			-
24menu_pattern			-
25menu_request_by_name		-
26menu_request_name		-
27menu_sub			-
28menu_term			-
29menu_userptr			-
30set_current_item		-
31set_item_init			-
32set_item_opts			-
33set_item_term			-
34set_item_userptr		-
35set_menu_grey			-
36set_menu_init			-
37set_menu_items			-
38set_menu_opts			-
39set_menu_pad			-
40set_menu_pattern		-
41set_menu_spacing		-
42set_menu_term			-
43set_menu_userptr		-
44set_top_row			-
45top_row				-
46*/
47
48#include <test.priv.h>
49
50#if USE_LIBMENU
51
52#include <menu.h>
53
54#include <sys/types.h>
55#include <sys/stat.h>
56
57#ifdef NCURSES_VERSION
58#ifdef TRACE
59static unsigned save_trace = TRACE_ORDINARY | TRACE_CALLS;
60extern unsigned _nc_tracing;
61static MENU *mpTrace;
62#endif
63#else
64#undef TRACE
65#endif
66
67typedef enum {
68    eUnknown = -1
69    ,eFile = 0
70    ,eSelect
71#ifdef TRACE
72    ,eTrace
73#endif
74} MenuNo;
75
76#define MENU_Y	1
77
78static MENU *mpBanner;
79static MENU *mpFile;
80static MENU *mpSelect;
81
82#if !HAVE_STRDUP
83#define strdup my_strdup
84static char *
85strdup(char *s)
86{
87    char *p = (char *) malloc(strlen(s) + 1);
88    if (p)
89	strcpy(p, s);
90    return (p);
91}
92#endif /* not HAVE_STRDUP */
93
94/* Common function to allow ^T to toggle trace-mode in the middle of a test
95 * so that trace-files can be made smaller.
96 */
97static int
98wGetchar(WINDOW *win)
99{
100    int c;
101#ifdef TRACE
102    while ((c = wgetch(win)) == CTRL('T')) {
103	if (_nc_tracing) {
104	    save_trace = _nc_tracing;
105	    _tracef("TOGGLE-TRACING OFF");
106	    _nc_tracing = 0;
107	} else {
108	    _nc_tracing = save_trace;
109	}
110	trace(_nc_tracing);
111	if (_nc_tracing)
112	    _tracef("TOGGLE-TRACING ON");
113    }
114#else
115    c = wgetch(win);
116#endif
117    return c;
118}
119#define Getchar() wGetchar(stdscr)
120
121static int
122menu_virtualize(int c)
123{
124    int result;
125
126    if (c == '\n' || c == KEY_EXIT)
127	result = (MAX_COMMAND + 1);
128    else if (c == 'u')
129	result = (REQ_SCR_ULINE);
130    else if (c == 'd')
131	result = (REQ_SCR_DLINE);
132    else if (c == 'b' || c == KEY_NPAGE)
133	result = (REQ_SCR_UPAGE);
134    else if (c == 'f' || c == KEY_PPAGE)
135	result = (REQ_SCR_DPAGE);
136    else if (c == 'l' || c == KEY_LEFT || c == KEY_BTAB)
137	result = (REQ_LEFT_ITEM);
138    else if (c == 'n' || c == KEY_DOWN)
139	result = (REQ_NEXT_ITEM);
140    else if (c == 'p' || c == KEY_UP)
141	result = (REQ_PREV_ITEM);
142    else if (c == 'r' || c == KEY_RIGHT || c == '\t')
143	result = (REQ_RIGHT_ITEM);
144    else if (c == ' ')
145	result = (REQ_TOGGLE_ITEM);
146    else {
147	if (c != KEY_MOUSE)
148	    beep();
149	result = (c);
150    }
151    return result;
152}
153
154static int
155menu_getc(MENU * m)
156{
157    return wGetchar(menu_win(m));
158}
159
160static int
161menu_offset(MenuNo number)
162{
163    int result = 0;
164
165    if ((int) number >= 0) {
166	int spc_desc, spc_rows, spc_cols;
167
168#ifdef NCURSES_VERSION
169	menu_spacing(mpBanner, &spc_desc, &spc_rows, &spc_cols);
170#else
171	spc_rows = 0;
172#endif
173
174	/* FIXME: MENU.itemlen seems the only way to get actual width of items */
175	result = number * (mpBanner->itemlen + spc_rows);
176    }
177    return result;
178}
179
180static MENU *
181menu_create(ITEM ** items, int count, int ncols, MenuNo number)
182{
183    MENU *result;
184    WINDOW *menuwin;
185    int mrows, mcols;
186    int y = ((int) number >= 0) ? MENU_Y : 0;
187    int x = menu_offset(number);
188    int margin = (y == MENU_Y) ? 1 : 0;
189    int maxcol = (ncols + x) < COLS ? ncols : (COLS - x - 1);
190    int maxrow = (count + 1) / ncols;
191
192    if ((maxrow + y) >= (LINES - 4))
193	maxrow = LINES - 4 - y;
194
195    result = new_menu(items);
196
197    if (has_colors()) {
198	set_menu_fore(result, COLOR_PAIR(1));
199	set_menu_back(result, COLOR_PAIR(2));
200    }
201
202    set_menu_format(result, maxrow, maxcol);
203    scale_menu(result, &mrows, &mcols);
204
205    if (mcols + (2 * margin + x) >= COLS)
206	mcols = COLS - (2 * margin + x);
207
208#ifdef TRACE
209    if (number == eTrace)
210	menu_opts_off(result, O_ONEVALUE);
211    else
212	menu_opts_on(result, O_ONEVALUE);
213#endif
214
215    menuwin = newwin(mrows + (2 * margin), mcols + (2 * margin), y, x);
216    set_menu_win(result, menuwin);
217    keypad(menuwin, TRUE);
218    if (margin)
219	box(menuwin, 0, 0);
220
221    set_menu_sub(result, derwin(menuwin, mrows, mcols, margin, margin));
222
223    post_menu(result);
224
225    return result;
226}
227
228static void
229menu_destroy(MENU * m)
230{
231    ITEM **ip;
232    int count;
233
234    if (m != 0) {
235	delwin(menu_win(m));
236
237	ip = menu_items(m);
238	count = item_count(m);
239
240	free_menu(m);
241#if 0
242	if (count > 0) {
243	    while (*ip) {
244		_tracef("freeing item %d:%d", ip - menu_items(m), count);
245		free_item(*ip++);
246	    }
247	}
248#endif
249    }
250}
251
252/* force the given menu to appear */
253static void
254menu_display(MENU * m)
255{
256    touchwin(menu_win(m));
257    wrefresh(menu_win(m));
258}
259
260/*****************************************************************************/
261
262static void
263build_file_menu(MenuNo number)
264{
265    static const char *labels[] =
266    {
267	"Exit",
268	(char *) 0
269    };
270    static ITEM *items[SIZEOF(labels)];
271
272    ITEM **ip = items;
273    const char **ap;
274
275    for (ap = labels; *ap; ap++)
276	*ip++ = new_item(*ap, "");
277    *ip = (ITEM *) 0;
278
279    mpFile = menu_create(items, SIZEOF(labels) - 1, 1, number);
280}
281
282static int
283perform_file_menu(int cmd)
284{
285    return menu_driver(mpFile, cmd);
286}
287
288/*****************************************************************************/
289
290static void
291build_select_menu(MenuNo number, char *filename)
292{
293    static const char *labels[] =
294    {
295	"Lions",
296	"Tigers",
297	"Bears",
298	"(Oh my!)",
299	"Newts",
300	"Platypi",
301	"Lemurs",
302	"(Oh really?!)",
303	"Leopards",
304	"Panthers",
305	"Pumas",
306	"Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs",
307	"Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs, Lions, Tigers, Bears, (Oh my!), Newts, Platypi, Lemurs",
308	(char *) 0
309    };
310    static ITEM **items;
311
312    ITEM **ip;
313    const char **ap = 0;
314    unsigned count = 0;
315
316    if (filename != 0) {
317	struct stat sb;
318	if (stat(filename, &sb) == 0
319	    && (sb.st_mode & S_IFMT) == S_IFREG
320	    && sb.st_size != 0) {
321	    unsigned size = sb.st_size;
322	    unsigned j, k;
323	    char *blob = malloc(size + 1);
324	    const char **list = (const char **) calloc(sizeof(*list), size + 1);
325
326	    items = (ITEM **) calloc(sizeof(ITEM *), size + 1);
327	    if (blob != 0 && list != 0) {
328		FILE *fp = fopen(filename, "r");
329		if (fp != 0) {
330		    if (fread(blob, sizeof(char), size, fp) == size) {
331			bool mark = TRUE;
332			for (j = k = 0; j < size; ++j) {
333			    if (mark) {
334				list[k++] = blob + j;
335				mark = FALSE;
336			    }
337			    if (blob[j] == '\n') {
338				blob[j] = '\0';
339				if (k > 0 && *list[k - 1] == '\0')
340				    --k;
341				mark = TRUE;
342			    } else if (blob[j] == '\t') {
343				blob[j] = ' ';	/* menu items are printable */
344			    }
345			}
346			list[k] = 0;
347			count = k;
348			ap = list;
349		    }
350		    fclose(fp);
351		}
352	    }
353	}
354    }
355    if (ap == 0) {
356	count = SIZEOF(labels) - 1;
357	items = (ITEM **) calloc(count + 1, sizeof(*items));
358	ap = labels;
359    }
360
361    ip = items;
362    while (*ap != 0)
363	*ip++ = new_item(*ap++, "");
364    *ip = 0;
365
366    mpSelect = menu_create(items, (int) count, 1, number);
367}
368
369static int
370perform_select_menu(int cmd)
371{
372    return menu_driver(mpSelect, cmd);
373}
374
375/*****************************************************************************/
376
377#ifdef TRACE
378#define T_TBL(name) { #name, name }
379static struct {
380    const char *name;
381    unsigned mask;
382} t_tbl[] = {
383
384    T_TBL(TRACE_DISABLE),
385	T_TBL(TRACE_TIMES),
386	T_TBL(TRACE_TPUTS),
387	T_TBL(TRACE_UPDATE),
388	T_TBL(TRACE_MOVE),
389	T_TBL(TRACE_CHARPUT),
390	T_TBL(TRACE_ORDINARY),
391	T_TBL(TRACE_CALLS),
392	T_TBL(TRACE_VIRTPUT),
393	T_TBL(TRACE_IEVENT),
394	T_TBL(TRACE_BITS),
395	T_TBL(TRACE_ICALLS),
396	T_TBL(TRACE_CCALLS),
397	T_TBL(TRACE_DATABASE),
398	T_TBL(TRACE_ATTRS),
399	T_TBL(TRACE_MAXIMUM),
400    {
401	(char *) 0, 0
402    }
403};
404
405static void
406build_trace_menu(MenuNo number)
407{
408    static ITEM *items[SIZEOF(t_tbl)];
409
410    ITEM **ip = items;
411    int n;
412
413    for (n = 0; t_tbl[n].name != 0; n++)
414	*ip++ = new_item(t_tbl[n].name, "");
415    *ip = (ITEM *) 0;
416
417    mpTrace = menu_create(items, SIZEOF(t_tbl) - 1, 2, number);
418}
419
420static char *
421tracetrace(unsigned tlevel)
422{
423    static char *buf;
424    int n;
425
426    if (buf == 0) {
427	size_t need = 12;
428	for (n = 0; t_tbl[n].name != 0; n++)
429	    need += strlen(t_tbl[n].name) + 2;
430	buf = (char *) malloc(need);
431    }
432    sprintf(buf, "0x%02x = {", tlevel);
433    if (tlevel == 0) {
434	sprintf(buf + strlen(buf), "%s, ", t_tbl[0].name);
435    } else {
436	for (n = 1; t_tbl[n].name != 0; n++)
437	    if ((tlevel & t_tbl[n].mask) == t_tbl[n].mask) {
438		strcat(buf, t_tbl[n].name);
439		strcat(buf, ", ");
440	    }
441    }
442    if (buf[strlen(buf) - 2] == ',')
443	buf[strlen(buf) - 2] = '\0';
444    return (strcat(buf, "}"));
445}
446
447/* fake a dynamically reconfigurable menu using the 0th entry to deselect
448 * the others
449 */
450static bool
451update_trace_menu(MENU * m)
452{
453    ITEM **items;
454    ITEM *i, **p;
455    bool changed = FALSE;
456
457    items = menu_items(m);
458    i = current_item(m);
459    if (i == items[0]) {
460	if (item_value(i)) {
461	    for (p = items + 1; *p != 0; p++)
462		if (item_value(*p)) {
463		    set_item_value(*p, FALSE);
464		    changed = TRUE;
465		}
466	}
467    }
468    return changed;
469}
470
471static int
472perform_trace_menu(int cmd)
473/* interactively set the trace level */
474{
475    ITEM **ip;
476    unsigned newtrace;
477    int result;
478
479    for (ip = menu_items(mpTrace); *ip; ip++) {
480	unsigned mask = t_tbl[item_index(*ip)].mask;
481	if (mask == 0)
482	    set_item_value(*ip, _nc_tracing == 0);
483	else if ((mask & _nc_tracing) == mask)
484	    set_item_value(*ip, TRUE);
485    }
486
487    result = menu_driver(mpTrace, cmd);
488
489    if (result == E_OK) {
490	if (update_trace_menu(mpTrace) || cmd == REQ_TOGGLE_ITEM) {
491	    newtrace = 0;
492	    for (ip = menu_items(mpTrace); *ip; ip++) {
493		if (item_value(*ip))
494		    newtrace |= t_tbl[item_index(*ip)].mask;
495	    }
496	    trace(newtrace);
497	    _tracef("trace level interactively set to %s", tracetrace(_nc_tracing));
498
499	    (void) mvprintw(LINES - 2, 0,
500			    "Trace level is %s\n", tracetrace(_nc_tracing));
501	    refresh();
502	}
503    }
504    return result;
505}
506#endif /* TRACE */
507
508/*****************************************************************************/
509
510static int
511menu_number(void)
512{
513    return item_index(current_item(mpBanner));
514}
515
516static MENU *
517current_menu(void)
518{
519    MENU *result;
520
521    switch (menu_number()) {
522    case eFile:
523	result = mpFile;
524	break;
525    case eSelect:
526	result = mpSelect;
527	break;
528#ifdef TRACE
529    case eTrace:
530	result = mpTrace;
531	break;
532#endif
533    default:
534	result = 0;
535	break;
536    }
537    return result;
538}
539
540static void
541build_menus(char *filename)
542{
543    static const char *labels[] =
544    {
545	"File",
546	"Select",
547#ifdef TRACE
548	"Trace",
549#endif
550	(char *) 0
551    };
552    static ITEM *items[SIZEOF(labels)];
553
554    ITEM **ip = items;
555    const char **ap;
556
557    for (ap = labels; *ap; ap++)
558	*ip++ = new_item(*ap, "");
559    *ip = (ITEM *) 0;
560
561    mpBanner = menu_create(items, SIZEOF(labels) - 1, SIZEOF(labels) - 1, eUnknown);
562    set_menu_mark(mpBanner, ">");
563
564    build_file_menu(eFile);
565    build_select_menu(eSelect, filename);
566#ifdef TRACE
567    build_trace_menu(eTrace);
568#endif
569}
570
571static void
572perform_menus(void)
573{
574    MENU *this_menu;
575    MENU *last_menu = mpFile;
576    int code = E_UNKNOWN_COMMAND, cmd, ch;
577
578#ifdef NCURSES_MOUSE_VERSION
579    mousemask(ALL_MOUSE_EVENTS, (mmask_t *) 0);
580#endif
581
582    menu_display(last_menu);
583
584    for (;;) {
585	ch = menu_getc(mpBanner);
586	cmd = menu_virtualize(ch);
587
588	switch (cmd) {
589	    /*
590	     * The banner menu acts solely to select one of the other menus.
591	     * Move between its items, wrapping at the left/right limits.
592	     */
593	case REQ_LEFT_ITEM:
594	case REQ_RIGHT_ITEM:
595	    code = menu_driver(mpBanner, cmd);
596	    if (code == E_REQUEST_DENIED) {
597		if (menu_number() > 0)
598		    code = menu_driver(mpBanner, REQ_FIRST_ITEM);
599		else
600		    code = menu_driver(mpBanner, REQ_LAST_ITEM);
601	    }
602	    break;
603	default:
604	    switch (menu_number()) {
605	    case eFile:
606		code = perform_file_menu(cmd);
607		break;
608	    case eSelect:
609		code = perform_select_menu(cmd);
610		break;
611#ifdef TRACE
612	    case eTrace:
613		code = perform_trace_menu(cmd);
614		break;
615#endif
616	    }
617
618	    if ((code == E_REQUEST_DENIED) && (cmd == KEY_MOUSE)) {
619		code = menu_driver(mpBanner, cmd);
620	    }
621
622	    break;
623	}
624
625	if (code == E_OK) {
626	    this_menu = current_menu();
627	    if (this_menu != last_menu) {
628		move(1, 0);
629		clrtobot();
630		box(menu_win(this_menu), 0, 0);
631		refresh();
632
633		/* force the current menu to appear */
634		menu_display(this_menu);
635
636		last_menu = this_menu;
637	    }
638	}
639	wrefresh(menu_win(last_menu));
640	if (code == E_UNKNOWN_COMMAND
641	    || code == E_NOT_POSTED) {
642	    if (menu_number() == eFile)
643		break;
644	    beep();
645	}
646	if (code == E_REQUEST_DENIED)
647	    beep();
648	continue;
649    }
650
651#ifdef NCURSES_MOUSE_VERSION
652    mousemask(0, (mmask_t *) 0);
653#endif
654}
655
656static void
657destroy_menus(void)
658{
659    menu_destroy(mpFile);
660    menu_destroy(mpSelect);
661#ifdef TRACE
662    menu_destroy(mpTrace);
663#endif
664    menu_destroy(mpBanner);
665}
666
667int
668main(int argc, char *argv[])
669{
670    setlocale(LC_ALL, "");
671
672    initscr();
673    noraw();
674    cbreak();
675    noecho();
676
677    if (has_colors()) {
678	start_color();
679	init_pair(1, COLOR_RED, COLOR_BLACK);
680	init_pair(2, COLOR_BLUE, COLOR_WHITE);
681    }
682    build_menus(argc > 1 ? argv[1] : 0);
683    perform_menus();
684    destroy_menus();
685
686    endwin();
687    return EXIT_SUCCESS;
688}
689#else
690int
691main(void)
692{
693    printf("This program requires the curses menu library\n");
694    ExitProgram(EXIT_FAILURE);
695}
696#endif
697