• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* -*- c -*- ------------------------------------------------------------- *
2  *
3  *   Copyright 2004-2005 Murali Krishnan Ganapathy - All Rights Reserved
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
8  *   Boston MA 02111-1307, USA; either version 2 of the License, or
9  *   (at your option) any later version; incorporated herein by reference.
10  *
11  * ----------------------------------------------------------------------- */
12 
13 #include "cmenu.h"
14 #include "com32io.h"
15 #include <stdlib.h>
16 #include <console.h>
17 
18 // Local Variables
19 static pt_menusystem ms;    // Pointer to the menusystem
20 char TITLESTR[] =
21     "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
22 char TITLELONG[] = " TITLE too long ";
23 char ITEMLONG[] = " ITEM too long ";
24 char ACTIONLONG[] = " ACTION too long ";
25 char STATUSLONG[] = " STATUS too long ";
26 char EMPTYSTR[] = "";
27 
28 /* Forward declarations */
29 int calc_visible(pt_menu menu, int first);
30 int next_visible(pt_menu menu, int index);
31 int prev_visible(pt_menu menu, int index);
32 int next_visible_sep(pt_menu menu, int index);
33 int prev_visible_sep(pt_menu menu, int index);
34 int calc_first_early(pt_menu menu, int curr);
35 int calc_first_late(pt_menu menu, int curr);
36 int isvisible(pt_menu menu, int first, int curr);
37 
38 /* Basic Menu routines */
39 
40 // This is same as inputc except it honors the ontimeout handler
41 // and calls it when needed. For the callee, there is no difference
42 // as this will not return unless a key has been pressed.
getch(void)43 static int getch(void)
44 {
45     t_timeout_handler th;
46     int key;
47     unsigned long i;
48 
49     // Wait until keypress if no handler specified
50     if ((ms->ontimeout == NULL) && (ms->ontotaltimeout == NULL))
51         return get_key(stdin, 0);
52 
53     th = ms->ontimeout;
54     for (;;) {
55         for (i = 0; i < ms->tm_numsteps; i++) {
56             key = get_key(stdin, ms->tm_stepsize);
57             if (key != KEY_NONE)
58                 return key;
59 
60             if ((ms->tm_total_timeout == 0) || (ms->ontotaltimeout == NULL))
61                 continue;   // Dont bother with calculations if no handler
62             ms->tm_sofar_timeout += ms->tm_stepsize;
63             if (ms->tm_sofar_timeout >= ms->tm_total_timeout) {
64                 th = ms->ontotaltimeout;
65                 ms->tm_sofar_timeout = 0;
66                 break;      // Get out of the for loop
67             }
68         }
69         if (!th)
70             continue;       // no handler
71         key = th();
72         switch (key) {
73         case CODE_ENTER:    // Pretend user hit enter
74             return KEY_ENTER;
75         case CODE_ESCAPE:   // Pretend user hit escape
76             return KEY_ESC;
77         default:
78             break;
79         }
80     }
81     return KEY_NONE;
82 }
83 
find_shortcut(pt_menu menu,uchar shortcut,int index)84 int find_shortcut(pt_menu menu, uchar shortcut, int index)
85 // Find the next index with specified shortcut key
86 {
87     int ans;
88     pt_menuitem mi;
89 
90     // Garbage in garbage out
91     if ((index < 0) || (index >= menu->numitems))
92     return index;
93     ans = index + 1;
94     // Go till end of menu
95     while (ans < menu->numitems) {
96     mi = menu->items[ans];
97     if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
98         || (mi->shortcut != shortcut))
99         ans++;
100     else
101         return ans;
102     }
103     // Start at the beginning and try again
104     ans = 0;
105     while (ans < index) {
106     mi = menu->items[ans];
107     if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
108         || (mi->shortcut != shortcut))
109         ans++;
110     else
111         return ans;
112     }
113     return index;       // Sorry not found
114 }
115 
116 /* Redraw background and title */
reset_ui(void)117 static void reset_ui(void)
118 {
119     uchar tpos;
120 
121     cls();
122     clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol,
123                 ms->fillchar, ms->fillattr);
124 
125     tpos = (ms->numcols - strlen(ms->title) - 1) >> 1;  // center it on line
126     gotoxy(ms->minrow, ms->mincol);
127     cprint(ms->tfillchar, ms->titleattr, ms->numcols);
128     gotoxy(ms->minrow, ms->mincol + tpos);
129     csprint(ms->title, ms->titleattr);
130 
131     cursoroff();
132 }
133 
134 /*
135  * Print a menu item
136  *
137  * attr[0] is non-hilite attr, attr[1] is highlight attr
138  */
printmenuitem(const char * str,uchar * attr)139 void printmenuitem(const char *str, uchar * attr)
140 {
141     int hlite = NOHLITE;    // Initially no highlighting
142 
143     while (*str) {
144         switch (*str) {
145             case BELL:      // No Bell Char
146                 break;
147             case ENABLEHLITE:   // Switch on highlighting
148                 hlite = HLITE;
149                 break;
150             case DISABLEHLITE:  // Turn off highlighting
151                 hlite = NOHLITE;
152                 break;
153             default:
154                 putch(*str, attr[hlite]);
155         }
156         str++;
157     }
158 }
159 
160 
161 /**
162  * print_line - Print a whole line in a menu
163  * @menu:   current menu to handle
164  * @curr:   index of the current entry highlighted
165  * @top:    top coordinate of the @menu
166  * @left:   left coordinate of the @menu
167  * @x:      index in the menu of curr
168  * @row:    row currently displayed
169  * @radio:  radio item?
170  **/
print_line(pt_menu menu,int curr,uchar top,uchar left,int x,int row,bool radio)171 static void print_line(pt_menu menu, int curr, uchar top, uchar left,
172                        int x, int row, bool radio)
173 {
174     pt_menuitem ci;
175     char fchar[6], lchar[6];    // The first and last char in for each entry
176     const char *str;            // Item string (cf printmenuitem)
177     char sep[MENULEN];          // Separator (OPT_SEP)
178     uchar *attr;                // Attribute
179     int menuwidth = menu->menuwidth + 3;
180 
181     if (row >= menu->menuheight)
182         return;
183 
184     ci = menu->items[x];
185 
186     memset(sep, ms->box_horiz, menuwidth);
187     sep[menuwidth - 1] = 0;
188 
189     // Setup the defaults now
190     if (radio) {
191         fchar[0] = '\b';
192         fchar[1] = SO;
193         fchar[2] = (x == curr ? RADIOSEL : RADIOUNSEL);
194         fchar[3] = SI;
195         fchar[4] = '\0';    // Unselected ( )
196         lchar[0] = '\0';    // Nothing special after
197         attr = ms->normalattr;  // Always same attribute
198     } else {
199         lchar[0] = fchar[0] = ' ';
200         lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces
201         attr = (x == curr ? ms->reverseattr : ms->normalattr);  // Normal attributes
202     }
203     str = ci->item;     // Pointer to item string
204     switch (ci->action) // set up attr,str,fchar,lchar for everything
205     {
206     case OPT_INACTIVE:
207         if (radio)
208             attr = ms->inactattr;
209         else
210             attr = (x == curr ? ms->revinactattr : ms->inactattr);
211         break;
212     case OPT_SUBMENU:
213         if (radio)
214             break;      // Not supported for radio menu
215         lchar[0] = '>';
216         lchar[1] = 0;
217         break;
218     case OPT_RADIOMENU:
219         if (radio)
220             break;      // Not supported for radio menu
221         lchar[0] = RADIOMENUCHAR;
222         lchar[1] = 0;
223         break;
224     case OPT_CHECKBOX:
225         if (radio)
226             break;      // Not supported for radio menu
227         lchar[0] = '\b';
228         lchar[1] = SO;
229         lchar[2] = (ci->itemdata.checked ? CHECKED : UNCHECKED);
230         lchar[3] = SI;
231         lchar[4] = 0;
232         break;
233     case OPT_SEP:
234         fchar[0] = '\b';
235         fchar[1] = SO;
236         fchar[2] = LEFT_MIDDLE_BORDER;
237         fchar[3] = MIDDLE_BORDER;
238         fchar[4] = MIDDLE_BORDER;
239         fchar[5] = 0;
240         memset(sep, MIDDLE_BORDER, menuwidth);
241         sep[menuwidth - 1] = 0;
242         str = sep;
243         lchar[0] = MIDDLE_BORDER;
244         lchar[1] = RIGHT_MIDDLE_BORDER;
245         lchar[2] = SI;
246         lchar[3] = 0;
247         break;
248     case OPT_EXITMENU:
249         if (radio)
250             break;      // Not supported for radio menu
251         fchar[0] = '<';
252         fchar[1] = 0;
253         break;
254     default:        // Just to keep the compiler happy
255         break;
256     }
257 
258     // Wipe area with spaces
259     gotoxy(top + row, left - 2);
260     cprint(ms->spacechar, attr[NOHLITE], menuwidth + 2);
261 
262     // Print first part
263     gotoxy(top + row, left - 2);
264     csprint(fchar, attr[NOHLITE]);
265 
266     // Print main part
267     gotoxy(top + row, left);
268     printmenuitem(str, attr);
269 
270     // Print last part
271     gotoxy(top + row, left + menuwidth - 1);
272     csprint(lchar, attr[NOHLITE]);
273 }
274 
275 // print the menu starting from FIRST
276 // will print a maximum of menu->menuheight items
printmenu(pt_menu menu,int curr,uchar top,uchar left,uchar first,bool radio)277 static void printmenu(pt_menu menu, int curr, uchar top, uchar left, uchar first, bool radio)
278 {
279     int x, row;         // x = index, row = position from top
280     int numitems, menuwidth;
281     pt_menuitem ci;
282 
283     numitems = calc_visible(menu, first);
284     if (numitems > menu->menuheight)
285     numitems = menu->menuheight;
286 
287     menuwidth = menu->menuwidth + 3;
288     clearwindow(top, left - 2, top + numitems + 1, left + menuwidth + 1,
289         ms->fillchar, ms->shadowattr);
290     drawbox(top - 1, left - 3, top + numitems, left + menuwidth,
291         ms->normalattr[NOHLITE]);
292 
293     // Menu title
294     x = (menuwidth - strlen(menu->title) - 1) >> 1;
295     gotoxy(top - 1, left + x);
296     printmenuitem(menu->title, ms->normalattr);
297 
298     // All lines in the menu
299     row = -1;           // 1 less than inital value of x
300     for (x = first; x < menu->numitems; x++) {
301         ci = menu->items[x];
302         if (ci->action == OPT_INVISIBLE)
303             continue;
304         row++;
305         if (row >= numitems)
306             break;      // Already have enough number of items
307         print_line(menu, curr, top, left, x, row, radio);
308     }
309     // Check if we need to MOREABOVE and MOREBELOW to be added
310     // reuse x
311     row = 0;
312     x = next_visible_sep(menu, 0);  // First item
313     if (!isvisible(menu, first, x)) // There is more above
314     {
315     row = 1;
316     gotoxy(top, left + menuwidth);
317     cprint(MOREABOVE, ms->normalattr[NOHLITE], 1);
318     }
319     x = prev_visible_sep(menu, menu->numitems); // last item
320     if (!isvisible(menu, first, x)) // There is more above
321     {
322     row = 1;
323     gotoxy(top + numitems - 1, left + menuwidth);
324     cprint(MOREBELOW, ms->normalattr[NOHLITE], 1);
325     }
326     // Add a scroll box
327     x = ((numitems - 1) * curr) / (menu->numitems);
328     if ((x > 0) && (row == 1)) {
329     gotoxy(top + x, left + menuwidth);
330     csprint("\016\141\017", ms->normalattr[NOHLITE]);
331     }
332     if (ms->handler)
333     ms->handler(ms, menu->items[curr]);
334 }
335 
cleanupmenu(pt_menu menu,uchar top,uchar left,int numitems)336 void cleanupmenu(pt_menu menu, uchar top, uchar left, int numitems)
337 {
338     if (numitems > menu->menuheight)
339     numitems = menu->menuheight;
340     clearwindow(top, left - 2, top + numitems + 1, left + menu->menuwidth + 4, ms->fillchar, ms->fillattr); // Clear the shadow
341     clearwindow(top - 1, left - 3, top + numitems, left + menu->menuwidth + 3, ms->fillchar, ms->fillattr); // main window
342 }
343 
344 
345 /* Handle one menu */
getmenuoption(pt_menu menu,uchar top,uchar left,uchar startopt,bool radio)346 static pt_menuitem getmenuoption(pt_menu menu, uchar top, uchar left, uchar startopt, bool radio)
347      // Return item chosen or NULL if ESC was hit.
348 {
349     int prev, prev_first, curr, i, first, tmp;
350     int asc = 0;
351     bool redraw = true; // Need to draw the menu the first time
352     uchar numitems;
353     pt_menuitem ci;     // Current item
354     t_handler_return hr;    // Return value of handler
355 
356     numitems = calc_visible(menu, 0);
357     // Setup status line
358     gotoxy(ms->minrow + ms->statline, ms->mincol);
359     cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
360 
361     // Initialise current menu item
362     curr = next_visible(menu, startopt);
363     prev = curr;
364 
365     gotoxy(ms->minrow + ms->statline, ms->mincol);
366     cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
367     gotoxy(ms->minrow + ms->statline, ms->mincol);
368     printmenuitem(menu->items[curr]->status, ms->statusattr);
369     first = calc_first_early(menu, curr);
370     prev_first = first;
371     while (1)           // Forever
372     {
373     /* Redraw everything if:
374      *  + we need to scroll (take care of scroll bars, ...)
375      *  + menuoption
376      */
377     if (prev_first != first || redraw) {
378         printmenu(menu, curr, top, left, first, radio);
379     } else {
380         /* Redraw only the highlighted entry */
381         print_line(menu, curr, top, left, prev, prev - first, radio);
382         print_line(menu, curr, top, left, curr, curr - first, radio);
383     }
384     redraw = false;
385     prev = curr;
386     prev_first = first;
387     ci = menu->items[curr];
388     asc = getch();
389     switch (asc) {
390         case KEY_CTRL('L'):
391         redraw = true;
392         break;
393     case KEY_HOME:
394         curr = next_visible(menu, 0);
395         first = calc_first_early(menu, curr);
396         break;
397     case KEY_END:
398         curr = prev_visible(menu, numitems - 1);
399         first = calc_first_late(menu, curr);
400         break;
401     case KEY_PGDN:
402         for (i = 0; i < 5; i++)
403         curr = next_visible(menu, curr + 1);
404         first = calc_first_late(menu, curr);
405         break;
406     case KEY_PGUP:
407         for (i = 0; i < 5; i++)
408         curr = prev_visible(menu, curr - 1);
409         first = calc_first_early(menu, curr);
410         break;
411     case KEY_UP:
412         curr = prev_visible(menu, curr - 1);
413         if (curr < first)
414         first = calc_first_early(menu, curr);
415         break;
416     case KEY_DOWN:
417         curr = next_visible(menu, curr + 1);
418         if (!isvisible(menu, first, curr))
419         first = calc_first_late(menu, curr);
420         break;
421     case KEY_LEFT:
422     case KEY_ESC:
423         return NULL;
424         break;
425     case KEY_RIGHT:
426     case KEY_ENTER:
427         if (ci->action == OPT_INACTIVE)
428         break;
429         if (ci->action == OPT_CHECKBOX)
430         break;
431         if (ci->action == OPT_SEP)
432         break;
433         if (ci->action == OPT_EXITMENU)
434         return NULL;    // As if we hit Esc
435         // If we are going into a radio menu, dont call handler, return ci
436         if (ci->action == OPT_RADIOMENU)
437         return ci;
438         if (ci->handler != NULL)    // Do we have a handler
439         {
440         hr = ci->handler(ms, ci);
441         if (hr.refresh) // Do we need to refresh
442         {
443             // Cleanup menu using old number of items
444             cleanupmenu(menu, top, left, numitems);
445             // Recalculate the number of items
446             numitems = calc_visible(menu, 0);
447             // Reprint the menu
448             printmenu(menu, curr, top, left, first, radio);
449         }
450         if (hr.valid)
451             return ci;
452         } else
453         return ci;
454         break;
455     case SPACECHAR:
456         if (ci->action != OPT_CHECKBOX)
457         break;
458         ci->itemdata.checked = !ci->itemdata.checked;
459         if (ci->handler != NULL)    // Do we have a handler
460         {
461         hr = ci->handler(ms, ci);
462         if (hr.refresh) // Do we need to refresh
463         {
464             // Cleanup menu using old number of items
465             cleanupmenu(menu, top, left, numitems);
466             // Recalculate the number of items
467             numitems = calc_visible(menu, 0);
468             // Reprint the menu
469             printmenu(menu, curr, top, left, first, radio);
470         }
471         }
472         break;
473     default:
474         // Check if this is a shortcut key
475         if (((asc >= 'A') && (asc <= 'Z')) ||
476         ((asc >= 'a') && (asc <= 'z')) ||
477         ((asc >= '0') && (asc <= '9'))) {
478         tmp = find_shortcut(menu, asc, curr);
479         if ((tmp > curr) && (!isvisible(menu, first, tmp)))
480             first = calc_first_late(menu, tmp);
481         if (tmp < curr)
482             first = calc_first_early(menu, tmp);
483         curr = tmp;
484         } else {
485         if (ms->keys_handler)   // Call extra keys handler
486             ms->keys_handler(ms, menu->items[curr], asc);
487 
488             /* The handler may have changed the UI, reset it on exit */
489             reset_ui();
490             // Cleanup menu using old number of items
491             cleanupmenu(menu, top, left, numitems);
492             // Recalculate the number of items
493             numitems = calc_visible(menu, 0);
494             // Reprint the menu
495             printmenu(menu, curr, top, left, first, radio);
496         }
497         break;
498     }
499     // Update status line
500     /* Erase the previous status */
501     gotoxy(ms->minrow + ms->statline, ms->mincol);
502     cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
503     /* Print the new status */
504     gotoxy(ms->minrow + ms->statline, ms->mincol);
505     printmenuitem(menu->items[curr]->status, ms->statusattr);
506     }
507     return NULL;        // Should never come here
508 }
509 
510 /* Handle the entire system of menu's. */
runmenusystem(uchar top,uchar left,pt_menu cmenu,uchar startopt,uchar menutype)511 pt_menuitem runmenusystem(uchar top, uchar left, pt_menu cmenu, uchar startopt,
512               uchar menutype)
513      /*
514       * cmenu
515       *    Which menu should be currently displayed
516       * top,left
517       *    What is the position of the top,left corner of the menu
518       * startopt
519       *    which menu item do I start with
520       * menutype
521       *    NORMALMENU or RADIOMENU
522       *
523       * Return Value:
524       *    Returns a pointer to the final item chosen, or NULL if nothing chosen.
525       */
526 {
527     pt_menuitem opt, choice;
528     uchar startat, mt;
529     uchar row, col;
530 
531     if (cmenu == NULL)
532     return NULL;
533 
534 startover:
535     // Set the menu height
536     cmenu->menuheight = ms->maxrow - top - 3;
537     if (cmenu->menuheight > ms->maxmenuheight)
538     cmenu->menuheight = ms->maxmenuheight;
539     if (menutype == NORMALMENU)
540     opt = getmenuoption(cmenu, top, left, startopt, false);
541     else            // menutype == RADIOMENU
542     opt = getmenuoption(cmenu, top, left, startopt, true);
543 
544     if (opt == NULL) {
545     // User hit Esc
546     cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
547     return NULL;
548     }
549     // Are we done with the menu system?
550     if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU)) {
551     cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
552     return opt;     // parent cleanup other menus
553     }
554     // Either radiomenu or submenu
555     // Do we have a valid menu number? The next hack uses the fact that
556     // itemdata.submenunum = itemdata.radiomenunum (since enum data type)
557     if (opt->itemdata.submenunum >= ms->nummenus)   // This is Bad....
558     {
559     gotoxy(12, 12); // Middle of screen
560     csprint("ERROR: Invalid submenu requested.", 0x07);
561     cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
562     return NULL;        // Pretend user hit esc
563     }
564     // Call recursively for submenu
565     // Position the submenu below the current item,
566     // covering half the current window (horizontally)
567     row = ms->menus[(unsigned int)opt->itemdata.submenunum]->row;
568     col = ms->menus[(unsigned int)opt->itemdata.submenunum]->col;
569     if (row == 0xFF)
570     row = top + opt->index + 2;
571     if (col == 0xFF)
572     col = left + 3 + (cmenu->menuwidth >> 1);
573     mt = (opt->action == OPT_SUBMENU ? NORMALMENU : RADIOMENU);
574     startat = 0;
575     if ((opt->action == OPT_RADIOMENU) && (opt->data != NULL))
576     startat = ((t_menuitem *) opt->data)->index;
577 
578     choice = runmenusystem(row, col,
579                ms->menus[(unsigned int)opt->itemdata.submenunum],
580                startat, mt);
581     if (opt->action == OPT_RADIOMENU) {
582     if (choice != NULL)
583         opt->data = (void *)choice; // store choice in data field
584     if (opt->handler != NULL)
585         opt->handler(ms, opt);
586     choice = NULL;      // Pretend user hit esc
587     }
588     if (choice == NULL)     // User hit Esc in submenu
589     {
590     // Startover
591     startopt = opt->index;
592     goto startover;
593     } else {
594     cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
595     return choice;
596     }
597 }
598 
599 // Finds the indexof the menu with given name
find_menu_num(const char * name)600 uchar find_menu_num(const char *name)
601 {
602     int i;
603     pt_menu m;
604 
605     if (name == NULL)
606     return (uchar) (-1);
607     for (i = 0; i < ms->nummenus; i++) {
608     m = ms->menus[i];
609     if ((m->name) && (strcmp(m->name, name) == 0))
610         return i;
611     }
612     return (uchar) (-1);
613 }
614 
615 // Run through all items and if they are submenus
616 // with a non-trivial "action" and trivial submenunum
617 // replace submenunum with the menu with name "action"
fix_submenus(void)618 void fix_submenus(void)
619 {
620     int i, j;
621     pt_menu m;
622     pt_menuitem mi;
623 
624     i = 0;
625     for (i = 0; i < ms->nummenus; i++) {
626     m = ms->menus[i];
627     for (j = 0; j < m->numitems; j++) {
628         mi = m->items[j];
629         // if item is a submenu and has non-empty non-trivial data string
630         if (mi->data && strlen(mi->data) > 0 &&
631         ((mi->action == OPT_SUBMENU)
632          || (mi->action == OPT_RADIOMENU))) {
633         mi->itemdata.submenunum = find_menu_num(mi->data);
634         }
635     }
636     }
637 }
638 
639 /* User Callable functions */
640 
showmenus(uchar startmenu)641 pt_menuitem showmenus(uchar startmenu)
642 {
643     pt_menuitem rv;
644 
645     fix_submenus();     // Fix submenu numbers incase nick names were used
646 
647     /* Turn autowrap off, to avoid scrolling the menu */
648     printf(CSI "?7l");
649 
650     // Setup screen for menusystem
651     reset_ui();
652 
653     // Go, main menu cannot be a radio menu
654     rv = runmenusystem(ms->minrow + MENUROW, ms->mincol + MENUCOL,
655                ms->menus[(unsigned int)startmenu], 0, NORMALMENU);
656 
657     // Hide the garbage we left on the screen
658     cls();
659     gotoxy(ms->minrow, ms->mincol);
660     cursoron();
661 
662     // Return user choice
663     return rv;
664 }
665 
init_menusystem(const char * title)666 pt_menusystem init_menusystem(const char *title)
667 {
668     int i;
669 
670     ms = NULL;
671     ms = (pt_menusystem) malloc(sizeof(t_menusystem));
672     if (ms == NULL)
673     return NULL;
674     ms->nummenus = 0;
675     // Initialise all menu pointers
676     for (i = 0; i < MAXMENUS; i++)
677     ms->menus[i] = NULL;
678 
679     ms->title = (char *)malloc(TITLELEN + 1);
680     if (title == NULL)
681     strcpy(ms->title, TITLESTR);    // Copy string
682     else
683     strcpy(ms->title, title);
684 
685     // Timeout settings
686     ms->tm_stepsize = TIMEOUTSTEPSIZE;
687     ms->tm_numsteps = TIMEOUTNUMSTEPS;
688 
689     ms->normalattr[NOHLITE] = NORMALATTR;
690     ms->normalattr[HLITE] = NORMALHLITE;
691 
692     ms->reverseattr[NOHLITE] = REVERSEATTR;
693     ms->reverseattr[HLITE] = REVERSEHLITE;
694 
695     ms->inactattr[NOHLITE] = INACTATTR;
696     ms->inactattr[HLITE] = INACTHLITE;
697 
698     ms->revinactattr[NOHLITE] = REVINACTATTR;
699     ms->revinactattr[HLITE] = REVINACTHLITE;
700 
701     ms->statusattr[NOHLITE] = STATUSATTR;
702     ms->statusattr[HLITE] = STATUSHLITE;
703 
704     ms->statline = STATLINE;
705     ms->tfillchar = TFILLCHAR;
706     ms->titleattr = TITLEATTR;
707 
708     ms->fillchar = FILLCHAR;
709     ms->fillattr = FILLATTR;
710     ms->spacechar = SPACECHAR;
711     ms->shadowattr = SHADOWATTR;
712 
713     ms->menupage = MENUPAGE;    // Usually no need to change this at all
714 
715     // Initialise all handlers
716     ms->handler = NULL;
717     ms->keys_handler = NULL;
718     ms->ontimeout = NULL;   // No timeout handler
719     ms->tm_total_timeout = 0;
720     ms->tm_sofar_timeout = 0;
721     ms->ontotaltimeout = NULL;
722 
723     // Setup ACTION_{,IN}VALID
724     ACTION_VALID.valid = 1;
725     ACTION_VALID.refresh = 0;
726     ACTION_INVALID.valid = 0;
727     ACTION_INVALID.refresh = 0;
728 
729     // Figure out the size of the screen we are in now.
730     // By default we use the whole screen for our menu
731     if (getscreensize(1, &ms->numrows, &ms->numcols)) {
732         /* Unknown screen size? */
733         ms->numcols = 80;
734         ms->numrows = 24;
735     }
736     ms->minrow = ms->mincol = 0;
737     ms->maxcol = ms->numcols - 1;
738     ms->maxrow = ms->numrows - 1;
739 
740     // How many entries per menu can we display at a time
741     ms->maxmenuheight = ms->maxrow - ms->minrow - 3;
742     if (ms->maxmenuheight > MAXMENUHEIGHT)
743     ms->maxmenuheight = MAXMENUHEIGHT;
744 
745     console_ansi_raw();
746 
747     return ms;
748 }
749 
set_normal_attr(uchar normal,uchar selected,uchar inactivenormal,uchar inactiveselected)750 void set_normal_attr(uchar normal, uchar selected, uchar inactivenormal,
751              uchar inactiveselected)
752 {
753     if (normal != 0xFF)
754     ms->normalattr[0] = normal;
755     if (selected != 0xFF)
756     ms->reverseattr[0] = selected;
757     if (inactivenormal != 0xFF)
758     ms->inactattr[0] = inactivenormal;
759     if (inactiveselected != 0xFF)
760     ms->revinactattr[0] = inactiveselected;
761 }
762 
set_normal_hlite(uchar normal,uchar selected,uchar inactivenormal,uchar inactiveselected)763 void set_normal_hlite(uchar normal, uchar selected, uchar inactivenormal,
764               uchar inactiveselected)
765 {
766     if (normal != 0xFF)
767     ms->normalattr[1] = normal;
768     if (selected != 0xFF)
769     ms->reverseattr[1] = selected;
770     if (inactivenormal != 0xFF)
771     ms->inactattr[1] = inactivenormal;
772     if (inactiveselected != 0xFF)
773     ms->revinactattr[1] = inactiveselected;
774 }
775 
set_status_info(uchar statusattr,uchar statushlite,uchar statline)776 void set_status_info(uchar statusattr, uchar statushlite, uchar statline)
777 {
778     if (statusattr != 0xFF)
779     ms->statusattr[NOHLITE] = statusattr;
780     if (statushlite != 0xFF)
781     ms->statusattr[HLITE] = statushlite;
782     // statline is relative to minrow
783     if (statline >= ms->numrows)
784     statline = ms->numrows - 1;
785     ms->statline = statline;    // relative to ms->minrow, 0 based
786 }
787 
set_title_info(uchar tfillchar,uchar titleattr)788 void set_title_info(uchar tfillchar, uchar titleattr)
789 {
790     if (tfillchar != 0xFF)
791     ms->tfillchar = tfillchar;
792     if (titleattr != 0xFF)
793     ms->titleattr = titleattr;
794 }
795 
set_misc_info(uchar fillchar,uchar fillattr,uchar spacechar,uchar shadowattr)796 void set_misc_info(uchar fillchar, uchar fillattr, uchar spacechar,
797            uchar shadowattr)
798 {
799     if (fillchar != 0xFF)
800     ms->fillchar = fillchar;
801     if (fillattr != 0xFF)
802     ms->fillattr = fillattr;
803     if (spacechar != 0xFF)
804     ms->spacechar = spacechar;
805     if (shadowattr != 0xFF)
806     ms->shadowattr = shadowattr;
807 }
808 
set_menu_options(uchar maxmenuheight)809 void set_menu_options(uchar maxmenuheight)
810 {
811     if (maxmenuheight != 0xFF)
812     ms->maxmenuheight = maxmenuheight;
813 }
814 
815 // Set the window which menusystem should use
set_window_size(uchar top,uchar left,uchar bot,uchar right)816 void set_window_size(uchar top, uchar left, uchar bot, uchar right)
817 {
818     int nr, nc;
819 
820     if ((top > bot) || (left > right))
821     return;         // Sorry no change will happen here
822 
823     if (getscreensize(1, &nr, &nc)) {
824         /* Unknown screen size? */
825         nr = 80;
826         nc = 24;
827     }
828     if (bot >= nr)
829     bot = nr - 1;
830     if (right >= nc)
831     right = nc - 1;
832     ms->minrow = top;
833     ms->mincol = left;
834     ms->maxrow = bot;
835     ms->maxcol = right;
836     ms->numcols = right - left + 1;
837     ms->numrows = bot - top + 1;
838     if (ms->statline >= ms->numrows)
839     ms->statline = ms->numrows - 1; // Clip statline if need be
840 }
841 
reg_handler(t_handler htype,void * handler)842 void reg_handler(t_handler htype, void *handler)
843 {
844     // If bad value set to default screen handler
845     switch (htype) {
846     case HDLR_KEYS:
847     ms->keys_handler = (t_keys_handler) handler;
848     break;
849     default:
850     ms->handler = (t_menusystem_handler) handler;
851     break;
852     }
853 }
854 
unreg_handler(t_handler htype)855 void unreg_handler(t_handler htype)
856 {
857     switch (htype) {
858     case HDLR_KEYS:
859     ms->keys_handler = NULL;
860     break;
861     default:
862     ms->handler = NULL;
863     break;
864     }
865 }
866 
reg_ontimeout(t_timeout_handler handler,unsigned int numsteps,unsigned int stepsize)867 void reg_ontimeout(t_timeout_handler handler, unsigned int numsteps,
868            unsigned int stepsize)
869 {
870     ms->ontimeout = handler;
871     if (numsteps != 0)
872     ms->tm_numsteps = numsteps;
873     if (stepsize != 0)
874     ms->tm_stepsize = stepsize;
875 }
876 
unreg_ontimeout(void)877 void unreg_ontimeout(void)
878 {
879     ms->ontimeout = NULL;
880 }
881 
reg_ontotaltimeout(t_timeout_handler handler,unsigned long numcentiseconds)882 void reg_ontotaltimeout(t_timeout_handler handler,
883             unsigned long numcentiseconds)
884 {
885     if (numcentiseconds != 0) {
886     ms->ontotaltimeout = handler;
887     ms->tm_total_timeout = numcentiseconds * 10;    // to convert to milliseconds
888     ms->tm_sofar_timeout = 0;
889     }
890 }
891 
unreg_ontotaltimeout(void)892 void unreg_ontotaltimeout(void)
893 {
894     ms->ontotaltimeout = NULL;
895 }
896 
next_visible(pt_menu menu,int index)897 int next_visible(pt_menu menu, int index)
898 {
899     int ans;
900     if (index < 0)
901     ans = 0;
902     else if (index >= menu->numitems)
903     ans = menu->numitems - 1;
904     else
905     ans = index;
906     while ((ans < menu->numitems - 1) &&
907        ((menu->items[ans]->action == OPT_INVISIBLE) ||
908         (menu->items[ans]->action == OPT_SEP)))
909     ans++;
910     return ans;
911 }
912 
prev_visible(pt_menu menu,int index)913 int prev_visible(pt_menu menu, int index)   // Return index of prev visible
914 {
915     int ans;
916     if (index < 0)
917     ans = 0;
918     else if (index >= menu->numitems)
919     ans = menu->numitems - 1;
920     else
921     ans = index;
922     while ((ans > 0) &&
923        ((menu->items[ans]->action == OPT_INVISIBLE) ||
924         (menu->items[ans]->action == OPT_SEP)))
925     ans--;
926     return ans;
927 }
928 
next_visible_sep(pt_menu menu,int index)929 int next_visible_sep(pt_menu menu, int index)
930 {
931     int ans;
932     if (index < 0)
933     ans = 0;
934     else if (index >= menu->numitems)
935     ans = menu->numitems - 1;
936     else
937     ans = index;
938     while ((ans < menu->numitems - 1) &&
939        (menu->items[ans]->action == OPT_INVISIBLE))
940     ans++;
941     return ans;
942 }
943 
prev_visible_sep(pt_menu menu,int index)944 int prev_visible_sep(pt_menu menu, int index)   // Return index of prev visible
945 {
946     int ans;
947     if (index < 0)
948     ans = 0;
949     else if (index >= menu->numitems)
950     ans = menu->numitems - 1;
951     else
952     ans = index;
953     while ((ans > 0) && (menu->items[ans]->action == OPT_INVISIBLE))
954     ans--;
955     return ans;
956 }
957 
calc_visible(pt_menu menu,int first)958 int calc_visible(pt_menu menu, int first)
959 {
960     int ans, i;
961 
962     if (menu == NULL)
963     return 0;
964     ans = 0;
965     for (i = first; i < menu->numitems; i++)
966     if (menu->items[i]->action != OPT_INVISIBLE)
967         ans++;
968     return ans;
969 }
970 
971 // is curr visible if first entry is first?
isvisible(pt_menu menu,int first,int curr)972 int isvisible(pt_menu menu, int first, int curr)
973 {
974     if (curr < first)
975     return 0;
976     return (calc_visible(menu, first) - calc_visible(menu, curr) <
977         menu->menuheight);
978 }
979 
980 // Calculate the first entry to be displayed
981 // so that curr is visible and make curr as late as possible
calc_first_late(pt_menu menu,int curr)982 int calc_first_late(pt_menu menu, int curr)
983 {
984     int ans, i, nv;
985 
986     nv = calc_visible(menu, 0);
987     if (nv <= menu->menuheight)
988     return 0;
989     // Start with curr and go back menu->menuheight times
990     ans = curr + 1;
991     for (i = 0; i < menu->menuheight; i++)
992     ans = prev_visible_sep(menu, ans - 1);
993     return ans;
994 }
995 
996 // Calculate the first entry to be displayed
997 // so that curr is visible and make curr as early as possible
calc_first_early(pt_menu menu,int curr)998 int calc_first_early(pt_menu menu, int curr)
999 {
1000     int ans, i, nv;
1001 
1002     nv = calc_visible(menu, 0);
1003     if (nv <= menu->menuheight)
1004     return 0;
1005     // Start with curr and go back till >= menu->menuheight
1006     // items are visible
1007     nv = calc_visible(menu, curr);  // Already nv of them are visible
1008     ans = curr;
1009     for (i = 0; i < menu->menuheight - nv; i++)
1010     ans = prev_visible_sep(menu, ans - 1);
1011     return ans;
1012 }
1013 
1014 // Create a new menu and return its position
add_menu(const char * title,int maxmenusize)1015 uchar add_menu(const char *title, int maxmenusize)
1016 {
1017     int num, i;
1018     pt_menu m;
1019 
1020     num = ms->nummenus;
1021     if (num >= MAXMENUS)
1022     return -1;
1023     m = NULL;
1024     m = (pt_menu) malloc(sizeof(t_menu));
1025     if (m == NULL)
1026     return -1;
1027     ms->menus[num] = m;
1028     m->numitems = 0;
1029     m->name = NULL;
1030     m->row = 0xFF;
1031     m->col = 0xFF;
1032     if (maxmenusize < 1)
1033     m->maxmenusize = MAXMENUSIZE;
1034     else
1035     m->maxmenusize = maxmenusize;
1036     m->items = (pt_menuitem *) malloc(sizeof(pt_menuitem) * (m->maxmenusize));
1037     for (i = 0; i < m->maxmenusize; i++)
1038     m->items[i] = NULL;
1039 
1040     m->title = (char *)malloc(MENULEN + 1);
1041     if (title) {
1042     if (strlen(title) > MENULEN - 2)
1043         strcpy(m->title, TITLELONG);
1044     else
1045         strcpy(m->title, title);
1046     } else
1047     strcpy(m->title, EMPTYSTR);
1048     m->menuwidth = strlen(m->title);
1049     ms->nummenus++;
1050     return ms->nummenus - 1;
1051 }
1052 
set_menu_name(const char * name)1053 void set_menu_name(const char *name)    // Set the "name" of this menu
1054 {
1055     pt_menu m;
1056 
1057     m = ms->menus[ms->nummenus - 1];
1058     if (m->name)        // Free up previous name
1059     {
1060     free(m->name);
1061     m->name = NULL;
1062     }
1063 
1064     if (name) {
1065     m->name = (char *)malloc(strlen(name) + 1);
1066     strcpy(m->name, name);
1067     }
1068 }
1069 
1070 // Create a new named menu and return its position
add_named_menu(const char * name,const char * title,int maxmenusize)1071 uchar add_named_menu(const char *name, const char *title, int maxmenusize)
1072 {
1073     add_menu(title, maxmenusize);
1074     set_menu_name(name);
1075     return ms->nummenus - 1;
1076 }
1077 
set_menu_pos(uchar row,uchar col)1078 void set_menu_pos(uchar row, uchar col) // Set the position of this menu.
1079 {
1080     pt_menu m;
1081 
1082     m = ms->menus[ms->nummenus - 1];
1083     m->row = row;
1084     m->col = col;
1085 }
1086 
add_sep(void)1087 pt_menuitem add_sep(void)       // Add a separator to current menu
1088 {
1089     pt_menuitem mi;
1090     pt_menu m;
1091 
1092     m = (ms->menus[ms->nummenus - 1]);
1093     mi = NULL;
1094     mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1095     if (mi == NULL)
1096     return NULL;
1097     m->items[(unsigned int)m->numitems] = mi;
1098     mi->handler = NULL;     // No handler
1099     mi->item = mi->status = mi->data = NULL;
1100     mi->action = OPT_SEP;
1101     mi->index = m->numitems++;
1102     mi->parindex = ms->nummenus - 1;
1103     mi->shortcut = 0;
1104     mi->helpid = 0;
1105     return mi;
1106 }
1107 
1108 // Add item to the "current" menu
add_item(const char * item,const char * status,t_action action,const char * data,uchar itemdata)1109 pt_menuitem add_item(const char *item, const char *status, t_action action,
1110              const char *data, uchar itemdata)
1111 {
1112     pt_menuitem mi;
1113     pt_menu m;
1114     const char *str;
1115     uchar inhlite = 0;      // Are we inside hlite area
1116 
1117     m = (ms->menus[ms->nummenus - 1]);
1118     mi = NULL;
1119     mi = (pt_menuitem) malloc(sizeof(t_menuitem));
1120     if (mi == NULL)
1121     return NULL;
1122     m->items[(unsigned int)m->numitems] = mi;
1123     mi->handler = NULL;     // No handler
1124 
1125     // Allocate space to store stuff
1126     mi->item = (char *)malloc(MENULEN + 1);
1127     mi->status = (char *)malloc(STATLEN + 1);
1128     mi->data = (char *)malloc(ACTIONLEN + 1);
1129 
1130     if (item) {
1131     if (strlen(item) > MENULEN) {
1132         strcpy(mi->item, ITEMLONG);
1133     } else {
1134         strcpy(mi->item, item);
1135     }
1136     if (strlen(mi->item) > m->menuwidth)
1137         m->menuwidth = strlen(mi->item);
1138     } else
1139     strcpy(mi->item, EMPTYSTR);
1140 
1141     if (status) {
1142     if (strlen(status) > STATLEN) {
1143         strcpy(mi->status, STATUSLONG);
1144     } else {
1145         strcpy(mi->status, status);
1146     }
1147     } else
1148     strcpy(mi->status, EMPTYSTR);
1149 
1150     mi->action = action;
1151     str = mi->item;
1152     mi->shortcut = 0;
1153     mi->helpid = 0xFFFF;
1154     inhlite = 0;        // We have not yet seen an ENABLEHLITE char
1155     // Find the first char in [A-Za-z0-9] after ENABLEHLITE and not arg to control char
1156     while (*str) {
1157     if (*str == ENABLEHLITE) {
1158         inhlite = 1;
1159     }
1160     if (*str == DISABLEHLITE) {
1161         inhlite = 0;
1162     }
1163     if ((inhlite == 1) &&
1164         (((*str >= 'A') && (*str <= 'Z')) ||
1165          ((*str >= 'a') && (*str <= 'z')) ||
1166          ((*str >= '0') && (*str <= '9')))) {
1167         mi->shortcut = *str;
1168         break;
1169     }
1170     ++str;
1171     }
1172     if ((mi->shortcut >= 'A') && (mi->shortcut <= 'Z')) // Make lower case
1173     mi->shortcut = mi->shortcut - 'A' + 'a';
1174 
1175     if (data) {
1176     if (strlen(data) > ACTIONLEN) {
1177         strcpy(mi->data, ACTIONLONG);
1178     } else {
1179         strcpy(mi->data, data);
1180     }
1181     } else
1182     strcpy(mi->data, EMPTYSTR);
1183 
1184     switch (action) {
1185     case OPT_SUBMENU:
1186     mi->itemdata.submenunum = itemdata;
1187     break;
1188     case OPT_CHECKBOX:
1189     mi->itemdata.checked = itemdata;
1190     break;
1191     case OPT_RADIOMENU:
1192     mi->itemdata.radiomenunum = itemdata;
1193     if (mi->data)
1194         free(mi->data);
1195     mi->data = NULL;    // No selection made
1196     break;
1197     default:            // to keep the compiler happy
1198     break;
1199     }
1200     mi->index = m->numitems++;
1201     mi->parindex = ms->nummenus - 1;
1202     return mi;
1203 }
1204 
1205 // Set the shortcut key for the current item
set_item_options(uchar shortcut,int helpid)1206 void set_item_options(uchar shortcut, int helpid)
1207 {
1208     pt_menuitem mi;
1209     pt_menu m;
1210 
1211     m = (ms->menus[ms->nummenus - 1]);
1212     if (m->numitems <= 0)
1213     return;
1214     mi = m->items[(unsigned int)m->numitems - 1];
1215 
1216     if (shortcut != 0xFF)
1217     mi->shortcut = shortcut;
1218     if (helpid != 0xFFFF)
1219     mi->helpid = helpid;
1220 }
1221 
1222 // Free internal datasutructures
close_menusystem(void)1223 void close_menusystem(void)
1224 {
1225 }
1226 
1227 // append_line_helper(pt_menu menu,char *line)
append_line_helper(int menunum,char * line)1228 void append_line_helper(int menunum, char *line)
1229 {
1230     pt_menu menu;
1231     pt_menuitem mi, ri;
1232     char *app;
1233     int ctr;
1234 
1235     menu = ms->menus[menunum];
1236     for (ctr = 0; ctr < (int)menu->numitems; ctr++) {
1237     mi = menu->items[ctr];
1238     app = NULL;     //What to append
1239     switch (mi->action) {
1240     case OPT_CHECKBOX:
1241         if (mi->itemdata.checked)
1242         app = mi->data;
1243         break;
1244     case OPT_RADIOMENU:
1245         if (mi->data) { // Some selection has been made
1246         ri = (pt_menuitem) (mi->data);
1247         app = ri->data;
1248         }
1249         break;
1250     case OPT_SUBMENU:
1251         append_line_helper(mi->itemdata.submenunum, line);
1252         break;
1253     default:
1254         break;
1255     }
1256     if (app) {
1257         strcat(line, " ");
1258         strcat(line, app);
1259     }
1260     }
1261 }
1262 
1263 // Generate string based on state of checkboxes and radioitem in given menu
1264 // Assume line points to large enough buffer
gen_append_line(const char * menu_name,char * line)1265 void gen_append_line(const char *menu_name, char *line)
1266 {
1267     int menunum;
1268 
1269     menunum = find_menu_num(menu_name);
1270     if (menunum < 0)
1271     return;         // No such menu
1272     append_line_helper(menunum, line);
1273 }
1274