syslinux/com32/cmenu/libmenu/menu.c
<<
>>
Prefs
   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 "menu.h"
  14#include "com32io.h"
  15#include <stdlib.h>
  16#include <console.h>
  17
  18// Local Variables
  19static pt_menusystem ms;    // Pointer to the menusystem
  20char TITLESTR[] =
  21    "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
  22char TITLELONG[] = " TITLE too long ";
  23char ITEMLONG[] = " ITEM too long ";
  24char ACTIONLONG[] = " ACTION too long ";
  25char STATUSLONG[] = " STATUS too long ";
  26char EMPTYSTR[] = "";
  27
  28/* Forward declarations */
  29int calc_visible(pt_menu menu, int first);
  30int next_visible(pt_menu menu, int index);
  31int prev_visible(pt_menu menu, int index);
  32int next_visible_sep(pt_menu menu, int index);
  33int prev_visible_sep(pt_menu menu, int index);
  34int calc_first_early(pt_menu menu, int curr);
  35int calc_first_late(pt_menu menu, int curr);
  36int 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.
  43static 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
  84int 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 */
 117static 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 */
 139void 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 **/
 171static 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
 277static 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
 336void 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 */
 346static 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. */
 511pt_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
 534startover:
 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
 600uchar 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"
 618void 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
 641pt_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
 666pt_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
 750void 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
 763void 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
 776void 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
 788void 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
 796void 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
 809void set_menu_options(uchar maxmenuheight)
 810{
 811    if (maxmenuheight != 0xFF)
 812    ms->maxmenuheight = maxmenuheight;
 813}
 814
 815// Set the window which menusystem should use
 816void 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
 842void 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
 855void 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
 867void 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
 877void unreg_ontimeout(void)
 878{
 879    ms->ontimeout = NULL;
 880}
 881
 882void 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
 892void unreg_ontotaltimeout(void)
 893{
 894    ms->ontotaltimeout = NULL;
 895}
 896
 897int 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
 913int 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
 929int 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
 944int 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
 958int 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?
 972int 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
 982int 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
 998int 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
1015uchar 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
1053void 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
1071uchar 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
1078void 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
1087pt_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
1109pt_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
1206void 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
1223void close_menusystem(void)
1224{
1225}
1226
1227// append_line_helper(pt_menu menu,char *line)
1228void 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
1265void 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
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.