Home | History | Annotate | Download | only in libmenu
      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.
     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 
     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 */
    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  */
    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  **/
    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
    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 
    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 */
    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. */
    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
    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"
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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 
    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 
    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 
    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 
    877 void unreg_ontimeout(void)
    878 {
    879     ms->ontimeout = NULL;
    880 }
    881 
    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 
    892 void unreg_ontotaltimeout(void)
    893 {
    894     ms->ontotaltimeout = NULL;
    895 }
    896 
    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 
    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 
    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 
    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 
    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?
    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
    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
    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
   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 
   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
   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 
   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 
   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
   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
   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
   1223 void close_menusystem(void)
   1224 {
   1225 }
   1226 
   1227 // append_line_helper(pt_menu menu,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
   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