/* -*- c -*- ------------------------------------------------------------- *
*
* Copyright 2004-2005 Murali Krishnan Ganapathy - All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 53 Temple Place Ste 330,
* Boston MA 02111-1307, USA; either version 2 of the License, or
* (at your option) any later version; incorporated herein by reference.
*
* ----------------------------------------------------------------------- */
#include "cmenu.h"
#include "com32io.h"
#include <stdlib.h>
#include <console.h>
// Local Variables
static pt_menusystem ms; // Pointer to the menusystem
char TITLESTR[] =
"COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
char TITLELONG[] = " TITLE too long ";
char ITEMLONG[] = " ITEM too long ";
char ACTIONLONG[] = " ACTION too long ";
char STATUSLONG[] = " STATUS too long ";
char EMPTYSTR[] = "";
/* Forward declarations */
int calc_visible(pt_menu menu, int first);
int next_visible(pt_menu menu, int index);
int prev_visible(pt_menu menu, int index);
int next_visible_sep(pt_menu menu, int index);
int prev_visible_sep(pt_menu menu, int index);
int calc_first_early(pt_menu menu, int curr);
int calc_first_late(pt_menu menu, int curr);
int isvisible(pt_menu menu, int first, int curr);
/* Basic Menu routines */
// This is same as inputc except it honors the ontimeout handler
// and calls it when needed. For the callee, there is no difference
// as this will not return unless a key has been pressed.
static int getch(void)
{
t_timeout_handler th;
int key;
unsigned long i;
// Wait until keypress if no handler specified
if ((ms->ontimeout == NULL) && (ms->ontotaltimeout == NULL))
return get_key(stdin, 0);
th = ms->ontimeout;
for (;;) {
for (i = 0; i < ms->tm_numsteps; i++) {
key = get_key(stdin, ms->tm_stepsize);
if (key != KEY_NONE)
return key;
if ((ms->tm_total_timeout == 0) || (ms->ontotaltimeout == NULL))
continue; // Dont bother with calculations if no handler
ms->tm_sofar_timeout += ms->tm_stepsize;
if (ms->tm_sofar_timeout >= ms->tm_total_timeout) {
th = ms->ontotaltimeout;
ms->tm_sofar_timeout = 0;
break; // Get out of the for loop
}
}
if (!th)
continue; // no handler
key = th();
switch (key) {
case CODE_ENTER: // Pretend user hit enter
return KEY_ENTER;
case CODE_ESCAPE: // Pretend user hit escape
return KEY_ESC;
default:
break;
}
}
return KEY_NONE;
}
int find_shortcut(pt_menu menu, uchar shortcut, int index)
// Find the next index with specified shortcut key
{
int ans;
pt_menuitem mi;
// Garbage in garbage out
if ((index < 0) || (index >= menu->numitems))
return index;
ans = index + 1;
// Go till end of menu
while (ans < menu->numitems) {
mi = menu->items[ans];
if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
|| (mi->shortcut != shortcut))
ans++;
else
return ans;
}
// Start at the beginning and try again
ans = 0;
while (ans < index) {
mi = menu->items[ans];
if ((mi->action == OPT_INVISIBLE) || (mi->action == OPT_SEP)
|| (mi->shortcut != shortcut))
ans++;
else
return ans;
}
return index; // Sorry not found
}
/* Redraw background and title */
static void reset_ui(void)
{
uchar tpos;
cls();
clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol,
ms->fillchar, ms->fillattr);
tpos = (ms->numcols - strlen(ms->title) - 1) >> 1; // center it on line
gotoxy(ms->minrow, ms->mincol);
cprint(ms->tfillchar, ms->titleattr, ms->numcols);
gotoxy(ms->minrow, ms->mincol + tpos);
csprint(ms->title, ms->titleattr);
cursoroff();
}
/*
* Print a menu item
*
* attr[0] is non-hilite attr, attr[1] is highlight attr
*/
void printmenuitem(const char *str, uchar * attr)
{
int hlite = NOHLITE; // Initially no highlighting
while (*str) {
switch (*str) {
case BELL: // No Bell Char
break;
case ENABLEHLITE: // Switch on highlighting
hlite = HLITE;
break;
case DISABLEHLITE: // Turn off highlighting
hlite = NOHLITE;
break;
default:
putch(*str, attr[hlite]);
}
str++;
}
}
/**
* print_line - Print a whole line in a menu
* @menu: current menu to handle
* @curr: index of the current entry highlighted
* @top: top coordinate of the @menu
* @left: left coordinate of the @menu
* @x: index in the menu of curr
* @row: row currently displayed
* @radio: radio item?
**/
static void print_line(pt_menu menu, int curr, uchar top, uchar left,
int x, int row, bool radio)
{
pt_menuitem ci;
char fchar[6], lchar[6]; // The first and last char in for each entry
const char *str; // Item string (cf printmenuitem)
char sep[MENULEN]; // Separator (OPT_SEP)
uchar *attr; // Attribute
int menuwidth = menu->menuwidth + 3;
if (row >= menu->menuheight)
return;
ci = menu->items[x];
memset(sep, ms->box_horiz, menuwidth);
sep[menuwidth - 1] = 0;
// Setup the defaults now
if (radio) {
fchar[0] = '\b';
fchar[1] = SO;
fchar[2] = (x == curr ? RADIOSEL : RADIOUNSEL);
fchar[3] = SI;
fchar[4] = '\0'; // Unselected ( )
lchar[0] = '\0'; // Nothing special after
attr = ms->normalattr; // Always same attribute
} else {
lchar[0] = fchar[0] = ' ';
lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces
attr = (x == curr ? ms->reverseattr : ms->normalattr); // Normal attributes
}
str = ci->item; // Pointer to item string
switch (ci->action) // set up attr,str,fchar,lchar for everything
{
case OPT_INACTIVE:
if (radio)
attr = ms->inactattr;
else
attr = (x == curr ? ms->revinactattr : ms->inactattr);
break;
case OPT_SUBMENU:
if (radio)
break; // Not supported for radio menu
lchar[0] = '>';
lchar[1] = 0;
break;
case OPT_RADIOMENU:
if (radio)
break; // Not supported for radio menu
lchar[0] = RADIOMENUCHAR;
lchar[1] = 0;
break;
case OPT_CHECKBOX:
if (radio)
break; // Not supported for radio menu
lchar[0] = '\b';
lchar[1] = SO;
lchar[2] = (ci->itemdata.checked ? CHECKED : UNCHECKED);
lchar[3] = SI;
lchar[4] = 0;
break;
case OPT_SEP:
fchar[0] = '\b';
fchar[1] = SO;
fchar[2] = LEFT_MIDDLE_BORDER;
fchar[3] = MIDDLE_BORDER;
fchar[4] = MIDDLE_BORDER;
fchar[5] = 0;
memset(sep, MIDDLE_BORDER, menuwidth);
sep[menuwidth - 1] = 0;
str = sep;
lchar[0] = MIDDLE_BORDER;
lchar[1] = RIGHT_MIDDLE_BORDER;
lchar[2] = SI;
lchar[3] = 0;
break;
case OPT_EXITMENU:
if (radio)
break; // Not supported for radio menu
fchar[0] = '<';
fchar[1] = 0;
break;
default: // Just to keep the compiler happy
break;
}
// Wipe area with spaces
gotoxy(top + row, left - 2);
cprint(ms->spacechar, attr[NOHLITE], menuwidth + 2);
// Print first part
gotoxy(top + row, left - 2);
csprint(fchar, attr[NOHLITE]);
// Print main part
gotoxy(top + row, left);
printmenuitem(str, attr);
// Print last part
gotoxy(top + row, left + menuwidth - 1);
csprint(lchar, attr[NOHLITE]);
}
// print the menu starting from FIRST
// will print a maximum of menu->menuheight items
static void printmenu(pt_menu menu, int curr, uchar top, uchar left, uchar first, bool radio)
{
int x, row; // x = index, row = position from top
int numitems, menuwidth;
pt_menuitem ci;
numitems = calc_visible(menu, first);
if (numitems > menu->menuheight)
numitems = menu->menuheight;
menuwidth = menu->menuwidth + 3;
clearwindow(top, left - 2, top + numitems + 1, left + menuwidth + 1,
ms->fillchar, ms->shadowattr);
drawbox(top - 1, left - 3, top + numitems, left + menuwidth,
ms->normalattr[NOHLITE]);
// Menu title
x = (menuwidth - strlen(menu->title) - 1) >> 1;
gotoxy(top - 1, left + x);
printmenuitem(menu->title, ms->normalattr);
// All lines in the menu
row = -1; // 1 less than inital value of x
for (x = first; x < menu->numitems; x++) {
ci = menu->items[x];
if (ci->action == OPT_INVISIBLE)
continue;
row++;
if (row >= numitems)
break; // Already have enough number of items
print_line(menu, curr, top, left, x, row, radio);
}
// Check if we need to MOREABOVE and MOREBELOW to be added
// reuse x
row = 0;
x = next_visible_sep(menu, 0); // First item
if (!isvisible(menu, first, x)) // There is more above
{
row = 1;
gotoxy(top, left + menuwidth);
cprint(MOREABOVE, ms->normalattr[NOHLITE], 1);
}
x = prev_visible_sep(menu, menu->numitems); // last item
if (!isvisible(menu, first, x)) // There is more above
{
row = 1;
gotoxy(top + numitems - 1, left + menuwidth);
cprint(MOREBELOW, ms->normalattr[NOHLITE], 1);
}
// Add a scroll box
x = ((numitems - 1) * curr) / (menu->numitems);
if ((x > 0) && (row == 1)) {
gotoxy(top + x, left + menuwidth);
csprint("\016\141\017", ms->normalattr[NOHLITE]);
}
if (ms->handler)
ms->handler(ms, menu->items[curr]);
}
void cleanupmenu(pt_menu menu, uchar top, uchar left, int numitems)
{
if (numitems > menu->menuheight)
numitems = menu->menuheight;
clearwindow(top, left - 2, top + numitems + 1, left + menu->menuwidth + 4, ms->fillchar, ms->fillattr); // Clear the shadow
clearwindow(top - 1, left - 3, top + numitems, left + menu->menuwidth + 3, ms->fillchar, ms->fillattr); // main window
}
/* Handle one menu */
static pt_menuitem getmenuoption(pt_menu menu, uchar top, uchar left, uchar startopt, bool radio)
// Return item chosen or NULL if ESC was hit.
{
int prev, prev_first, curr, i, first, tmp;
int asc = 0;
bool redraw = true; // Need to draw the menu the first time
uchar numitems;
pt_menuitem ci; // Current item
t_handler_return hr; // Return value of handler
numitems = calc_visible(menu, 0);
// Setup status line
gotoxy(ms->minrow + ms->statline, ms->mincol);
cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
// Initialise current menu item
curr = next_visible(menu, startopt);
prev = curr;
gotoxy(ms->minrow + ms->statline, ms->mincol);
cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
gotoxy(ms->minrow + ms->statline, ms->mincol);
printmenuitem(menu->items[curr]->status, ms->statusattr);
first = calc_first_early(menu, curr);
prev_first = first;
while (1) // Forever
{
/* Redraw everything if:
* + we need to scroll (take care of scroll bars, ...)
* + menuoption
*/
if (prev_first != first || redraw) {
printmenu(menu, curr, top, left, first, radio);
} else {
/* Redraw only the highlighted entry */
print_line(menu, curr, top, left, prev, prev - first, radio);
print_line(menu, curr, top, left, curr, curr - first, radio);
}
redraw = false;
prev = curr;
prev_first = first;
ci = menu->items[curr];
asc = getch();
switch (asc) {
case KEY_CTRL('L'):
redraw = true;
break;
case KEY_HOME:
curr = next_visible(menu, 0);
first = calc_first_early(menu, curr);
break;
case KEY_END:
curr = prev_visible(menu, numitems - 1);
first = calc_first_late(menu, curr);
break;
case KEY_PGDN:
for (i = 0; i < 5; i++)
curr = next_visible(menu, curr + 1);
first = calc_first_late(menu, curr);
break;
case KEY_PGUP:
for (i = 0; i < 5; i++)
curr = prev_visible(menu, curr - 1);
first = calc_first_early(menu, curr);
break;
case KEY_UP:
curr = prev_visible(menu, curr - 1);
if (curr < first)
first = calc_first_early(menu, curr);
break;
case KEY_DOWN:
curr = next_visible(menu, curr + 1);
if (!isvisible(menu, first, curr))
first = calc_first_late(menu, curr);
break;
case KEY_LEFT:
case KEY_ESC:
return NULL;
break;
case KEY_RIGHT:
case KEY_ENTER:
if (ci->action == OPT_INACTIVE)
break;
if (ci->action == OPT_CHECKBOX)
break;
if (ci->action == OPT_SEP)
break;
if (ci->action == OPT_EXITMENU)
return NULL; // As if we hit Esc
// If we are going into a radio menu, dont call handler, return ci
if (ci->action == OPT_RADIOMENU)
return ci;
if (ci->handler != NULL) // Do we have a handler
{
hr = ci->handler(ms, ci);
if (hr.refresh) // Do we need to refresh
{
// Cleanup menu using old number of items
cleanupmenu(menu, top, left, numitems);
// Recalculate the number of items
numitems = calc_visible(menu, 0);
// Reprint the menu
printmenu(menu, curr, top, left, first, radio);
}
if (hr.valid)
return ci;
} else
return ci;
break;
case SPACECHAR:
if (ci->action != OPT_CHECKBOX)
break;
ci->itemdata.checked = !ci->itemdata.checked;
if (ci->handler != NULL) // Do we have a handler
{
hr = ci->handler(ms, ci);
if (hr.refresh) // Do we need to refresh
{
// Cleanup menu using old number of items
cleanupmenu(menu, top, left, numitems);
// Recalculate the number of items
numitems = calc_visible(menu, 0);
// Reprint the menu
printmenu(menu, curr, top, left, first, radio);
}
}
break;
default:
// Check if this is a shortcut key
if (((asc >= 'A') && (asc <= 'Z')) ||
((asc >= 'a') && (asc <= 'z')) ||
((asc >= '0') && (asc <= '9'))) {
tmp = find_shortcut(menu, asc, curr);
if ((tmp > curr) && (!isvisible(menu, first, tmp)))
first = calc_first_late(menu, tmp);
if (tmp < curr)
first = calc_first_early(menu, tmp);
curr = tmp;
} else {
if (ms->keys_handler) // Call extra keys handler
ms->keys_handler(ms, menu->items[curr], asc);
/* The handler may have changed the UI, reset it on exit */
reset_ui();
// Cleanup menu using old number of items
cleanupmenu(menu, top, left, numitems);
// Recalculate the number of items
numitems = calc_visible(menu, 0);
// Reprint the menu
printmenu(menu, curr, top, left, first, radio);
}
break;
}
// Update status line
/* Erase the previous status */
gotoxy(ms->minrow + ms->statline, ms->mincol);
cprint(ms->spacechar, ms->statusattr[NOHLITE], ms->numcols);
/* Print the new status */
gotoxy(ms->minrow + ms->statline, ms->mincol);
printmenuitem(menu->items[curr]->status, ms->statusattr);
}
return NULL; // Should never come here
}
/* Handle the entire system of menu's. */
pt_menuitem runmenusystem(uchar top, uchar left, pt_menu cmenu, uchar startopt,
uchar menutype)
/*
* cmenu
* Which menu should be currently displayed
* top,left
* What is the position of the top,left corner of the menu
* startopt
* which menu item do I start with
* menutype
* NORMALMENU or RADIOMENU
*
* Return Value:
* Returns a pointer to the final item chosen, or NULL if nothing chosen.
*/
{
pt_menuitem opt, choice;
uchar startat, mt;
uchar row, col;
if (cmenu == NULL)
return NULL;
startover:
// Set the menu height
cmenu->menuheight = ms->maxrow - top - 3;
if (cmenu->menuheight > ms->maxmenuheight)
cmenu->menuheight = ms->maxmenuheight;
if (menutype == NORMALMENU)
opt = getmenuoption(cmenu, top, left, startopt, false);
else // menutype == RADIOMENU
opt = getmenuoption(cmenu, top, left, startopt, true);
if (opt == NULL) {
// User hit Esc
cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
return NULL;
}
// Are we done with the menu system?
if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU)) {
cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
return opt; // parent cleanup other menus
}
// Either radiomenu or submenu
// Do we have a valid menu number? The next hack uses the fact that
// itemdata.submenunum = itemdata.radiomenunum (since enum data type)
if (opt->itemdata.submenunum >= ms->nummenus) // This is Bad....
{
gotoxy(12, 12); // Middle of screen
csprint("ERROR: Invalid submenu requested.", 0x07);
cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
return NULL; // Pretend user hit esc
}
// Call recursively for submenu
// Position the submenu below the current item,
// covering half the current window (horizontally)
row = ms->menus[(unsigned int)opt->itemdata.submenunum]->row;
col = ms->menus[(unsigned int)opt->itemdata.submenunum]->col;
if (row == 0xFF)
row = top + opt->index + 2;
if (col == 0xFF)
col = left + 3 + (cmenu->menuwidth >> 1);
mt = (opt->action == OPT_SUBMENU ? NORMALMENU : RADIOMENU);
startat = 0;
if ((opt->action == OPT_RADIOMENU) && (opt->data != NULL))
startat = ((t_menuitem *) opt->data)->index;
choice = runmenusystem(row, col,
ms->menus[(unsigned int)opt->itemdata.submenunum],
startat, mt);
if (opt->action == OPT_RADIOMENU) {
if (choice != NULL)
opt->data = (void *)choice; // store choice in data field
if (opt->handler != NULL)
opt->handler(ms, opt);
choice = NULL; // Pretend user hit esc
}
if (choice == NULL) // User hit Esc in submenu
{
// Startover
startopt = opt->index;
goto startover;
} else {
cleanupmenu(cmenu, top, left, calc_visible(cmenu, 0));
return choice;
}
}
// Finds the indexof the menu with given name
uchar find_menu_num(const char *name)
{
int i;
pt_menu m;
if (name == NULL)
return (uchar) (-1);
for (i = 0; i < ms->nummenus; i++) {
m = ms->menus[i];
if ((m->name) && (strcmp(m->name, name) == 0))
return i;
}
return (uchar) (-1);
}
// Run through all items and if they are submenus
// with a non-trivial "action" and trivial submenunum
// replace submenunum with the menu with name "action"
void fix_submenus(void)
{
int i, j;
pt_menu m;
pt_menuitem mi;
i = 0;
for (i = 0; i < ms->nummenus; i++) {
m = ms->menus[i];
for (j = 0; j < m->numitems; j++) {
mi = m->items[j];
// if item is a submenu and has non-empty non-trivial data string
if (mi->data && strlen(mi->data) > 0 &&
((mi->action == OPT_SUBMENU)
|| (mi->action == OPT_RADIOMENU))) {
mi->itemdata.submenunum = find_menu_num(mi->data);
}
}
}
}
/* User Callable functions */
pt_menuitem showmenus(uchar startmenu)
{
pt_menuitem rv;
fix_submenus(); // Fix submenu numbers incase nick names were used
/* Turn autowrap off, to avoid scrolling the menu */
printf(CSI "?7l");
// Setup screen for menusystem
reset_ui();
// Go, main menu cannot be a radio menu
rv = runmenusystem(ms->minrow + MENUROW, ms->mincol + MENUCOL,
ms->menus[(unsigned int)startmenu], 0, NORMALMENU);
// Hide the garbage we left on the screen
cls();
gotoxy(ms->minrow, ms->mincol);
cursoron();
// Return user choice
return rv;
}
pt_menusystem init_menusystem(const char *title)
{
int i;
ms = NULL;
ms = (pt_menusystem) malloc(sizeof(t_menusystem));
if (ms == NULL)
return NULL;
ms->nummenus = 0;
// Initialise all menu pointers
for (i = 0; i < MAXMENUS; i++)
ms->menus[i] = NULL;
ms->title = (char *)malloc(TITLELEN + 1);
if (title == NULL)
strcpy(ms->title, TITLESTR); // Copy string
else
strcpy(ms->title, title);
// Timeout settings
ms->tm_stepsize = TIMEOUTSTEPSIZE;
ms->tm_numsteps = TIMEOUTNUMSTEPS;
ms->normalattr[NOHLITE] = NORMALATTR;
ms->normalattr[HLITE] = NORMALHLITE;
ms->reverseattr[NOHLITE] = REVERSEATTR;
ms->reverseattr[HLITE] = REVERSEHLITE;
ms->inactattr[NOHLITE] = INACTATTR;
ms->inactattr[HLITE] = INACTHLITE;
ms->revinactattr[NOHLITE] = REVINACTATTR;
ms->revinactattr[HLITE] = REVINACTHLITE;
ms->statusattr[NOHLITE] = STATUSATTR;
ms->statusattr[HLITE] = STATUSHLITE;
ms->statline = STATLINE;
ms->tfillchar = TFILLCHAR;
ms->titleattr = TITLEATTR;
ms->fillchar = FILLCHAR;
ms->fillattr = FILLATTR;
ms->spacechar = SPACECHAR;
ms->shadowattr = SHADOWATTR;
ms->menupage = MENUPAGE; // Usually no need to change this at all
// Initialise all handlers
ms->handler = NULL;
ms->keys_handler = NULL;
ms->ontimeout = NULL; // No timeout handler
ms->tm_total_timeout = 0;
ms->tm_sofar_timeout = 0;
ms->ontotaltimeout = NULL;
// Setup ACTION_{,IN}VALID
ACTION_VALID.valid = 1;
ACTION_VALID.refresh = 0;
ACTION_INVALID.valid = 0;
ACTION_INVALID.refresh = 0;
// Figure out the size of the screen we are in now.
// By default we use the whole screen for our menu
if (getscreensize(1, &ms->numrows, &ms->numcols)) {
/* Unknown screen size? */
ms->numcols = 80;
ms->numrows = 24;
}
ms->minrow = ms->mincol = 0;
ms->maxcol = ms->numcols - 1;
ms->maxrow = ms->numrows - 1;
// How many entries per menu can we display at a time
ms->maxmenuheight = ms->maxrow - ms->minrow - 3;
if (ms->maxmenuheight > MAXMENUHEIGHT)
ms->maxmenuheight = MAXMENUHEIGHT;
console_ansi_raw();
return ms;
}
void set_normal_attr(uchar normal, uchar selected, uchar inactivenormal,
uchar inactiveselected)
{
if (normal != 0xFF)
ms->normalattr[0] = normal;
if (selected != 0xFF)
ms->reverseattr[0] = selected;
if (inactivenormal != 0xFF)
ms->inactattr[0] = inactivenormal;
if (inactiveselected != 0xFF)
ms->revinactattr[0] = inactiveselected;
}
void set_normal_hlite(uchar normal, uchar selected, uchar inactivenormal,
uchar inactiveselected)
{
if (normal != 0xFF)
ms->normalattr[1] = normal;
if (selected != 0xFF)
ms->reverseattr[1] = selected;
if (inactivenormal != 0xFF)
ms->inactattr[1] = inactivenormal;
if (inactiveselected != 0xFF)
ms->revinactattr[1] = inactiveselected;
}
void set_status_info(uchar statusattr, uchar statushlite, uchar statline)
{
if (statusattr != 0xFF)
ms->statusattr[NOHLITE] = statusattr;
if (statushlite != 0xFF)
ms->statusattr[HLITE] = statushlite;
// statline is relative to minrow
if (statline >= ms->numrows)
statline = ms->numrows - 1;
ms->statline = statline; // relative to ms->minrow, 0 based
}
void set_title_info(uchar tfillchar, uchar titleattr)
{
if (tfillchar != 0xFF)
ms->tfillchar = tfillchar;
if (titleattr != 0xFF)
ms->titleattr = titleattr;
}
void set_misc_info(uchar fillchar, uchar fillattr, uchar spacechar,
uchar shadowattr)
{
if (fillchar != 0xFF)
ms->fillchar = fillchar;
if (fillattr != 0xFF)
ms->fillattr = fillattr;
if (spacechar != 0xFF)
ms->spacechar = spacechar;
if (shadowattr != 0xFF)
ms->shadowattr = shadowattr;
}
void set_menu_options(uchar maxmenuheight)
{
if (maxmenuheight != 0xFF)
ms->maxmenuheight = maxmenuheight;
}
// Set the window which menusystem should use
void set_window_size(uchar top, uchar left, uchar bot, uchar right)
{
int nr, nc;
if ((top > bot) || (left > right))
return; // Sorry no change will happen here
if (getscreensize(1, &nr, &nc)) {
/* Unknown screen size? */
nr = 80;
nc = 24;
}
if (bot >= nr)
bot = nr - 1;
if (right >= nc)
right = nc - 1;
ms->minrow = top;
ms->mincol = left;
ms->maxrow = bot;
ms->maxcol = right;
ms->numcols = right - left + 1;
ms->numrows = bot - top + 1;
if (ms->statline >= ms->numrows)
ms->statline = ms->numrows - 1; // Clip statline if need be
}
void reg_handler(t_handler htype, void *handler)
{
// If bad value set to default screen handler
switch (htype) {
case HDLR_KEYS:
ms->keys_handler = (t_keys_handler) handler;
break;
default:
ms->handler = (t_menusystem_handler) handler;
break;
}
}
void unreg_handler(t_handler htype)
{
switch (htype) {
case HDLR_KEYS:
ms->keys_handler = NULL;
break;
default:
ms->handler = NULL;
break;
}
}
void reg_ontimeout(t_timeout_handler handler, unsigned int numsteps,
unsigned int stepsize)
{
ms->ontimeout = handler;
if (numsteps != 0)
ms->tm_numsteps = numsteps;
if (stepsize != 0)
ms->tm_stepsize = stepsize;
}
void unreg_ontimeout(void)
{
ms->ontimeout = NULL;
}
void reg_ontotaltimeout(t_timeout_handler handler,
unsigned long numcentiseconds)
{
if (numcentiseconds != 0) {
ms->ontotaltimeout = handler;
ms->tm_total_timeout = numcentiseconds * 10; // to convert to milliseconds
ms->tm_sofar_timeout = 0;
}
}
void unreg_ontotaltimeout(void)
{
ms->ontotaltimeout = NULL;
}
int next_visible(pt_menu menu, int index)
{
int ans;
if (index < 0)
ans = 0;
else if (index >= menu->numitems)
ans = menu->numitems - 1;
else
ans = index;
while ((ans < menu->numitems - 1) &&
((menu->items[ans]->action == OPT_INVISIBLE) ||
(menu->items[ans]->action == OPT_SEP)))
ans++;
return ans;
}
int prev_visible(pt_menu menu, int index) // Return index of prev visible
{
int ans;
if (index < 0)
ans = 0;
else if (index >= menu->numitems)
ans = menu->numitems - 1;
else
ans = index;
while ((ans > 0) &&
((menu->items[ans]->action == OPT_INVISIBLE) ||
(menu->items[ans]->action == OPT_SEP)))
ans--;
return ans;
}
int next_visible_sep(pt_menu menu, int index)
{
int ans;
if (index < 0)
ans = 0;
else if (index >= menu->numitems)
ans = menu->numitems - 1;
else
ans = index;
while ((ans < menu->numitems - 1) &&
(menu->items[ans]->action == OPT_INVISIBLE))
ans++;
return ans;
}
int prev_visible_sep(pt_menu menu, int index) // Return index of prev visible
{
int ans;
if (index < 0)
ans = 0;
else if (index >= menu->numitems)
ans = menu->numitems - 1;
else
ans = index;
while ((ans > 0) && (menu->items[ans]->action == OPT_INVISIBLE))
ans--;
return ans;
}
int calc_visible(pt_menu menu, int first)
{
int ans, i;
if (menu == NULL)
return 0;
ans = 0;
for (i = first; i < menu->numitems; i++)
if (menu->items[i]->action != OPT_INVISIBLE)
ans++;
return ans;
}
// is curr visible if first entry is first?
int isvisible(pt_menu menu, int first, int curr)
{
if (curr < first)
return 0;
return (calc_visible(menu, first) - calc_visible(menu, curr) <
menu->menuheight);
}
// Calculate the first entry to be displayed
// so that curr is visible and make curr as late as possible
int calc_first_late(pt_menu menu, int curr)
{
int ans, i, nv;
nv = calc_visible(menu, 0);
if (nv <= menu->menuheight)
return 0;
// Start with curr and go back menu->menuheight times
ans = curr + 1;
for (i = 0; i < menu->menuheight; i++)
ans = prev_visible_sep(menu, ans - 1);
return ans;
}
// Calculate the first entry to be displayed
// so that curr is visible and make curr as early as possible
int calc_first_early(pt_menu menu, int curr)
{
int ans, i, nv;
nv = calc_visible(menu, 0);
if (nv <= menu->menuheight)
return 0;
// Start with curr and go back till >= menu->menuheight
// items are visible
nv = calc_visible(menu, curr); // Already nv of them are visible
ans = curr;
for (i = 0; i < menu->menuheight - nv; i++)
ans = prev_visible_sep(menu, ans - 1);
return ans;
}
// Create a new menu and return its position
uchar add_menu(const char *title, int maxmenusize)
{
int num, i;
pt_menu m;
num = ms->nummenus;
if (num >= MAXMENUS)
return -1;
m = NULL;
m = (pt_menu) malloc(sizeof(t_menu));
if (m == NULL)
return -1;
ms->menus[num] = m;
m->numitems = 0;
m->name = NULL;
m->row = 0xFF;
m->col = 0xFF;
if (maxmenusize < 1)
m->maxmenusize = MAXMENUSIZE;
else
m->maxmenusize = maxmenusize;
m->items = (pt_menuitem *) malloc(sizeof(pt_menuitem) * (m->maxmenusize));
for (i = 0; i < m->maxmenusize; i++)
m->items[i] = NULL;
m->title = (char *)malloc(MENULEN + 1);
if (title) {
if (strlen(title) > MENULEN - 2)
strcpy(m->title, TITLELONG);
else
strcpy(m->title, title);
} else
strcpy(m->title, EMPTYSTR);
m->menuwidth = strlen(m->title);
ms->nummenus++;
return ms->nummenus - 1;
}
void set_menu_name(const char *name) // Set the "name" of this menu
{
pt_menu m;
m = ms->menus[ms->nummenus - 1];
if (m->name) // Free up previous name
{
free(m->name);
m->name = NULL;
}
if (name) {
m->name = (char *)malloc(strlen(name) + 1);
strcpy(m->name, name);
}
}
// Create a new named menu and return its position
uchar add_named_menu(const char *name, const char *title, int maxmenusize)
{
add_menu(title, maxmenusize);
set_menu_name(name);
return ms->nummenus - 1;
}
void set_menu_pos(uchar row, uchar col) // Set the position of this menu.
{
pt_menu m;
m = ms->menus[ms->nummenus - 1];
m->row = row;
m->col = col;
}
pt_menuitem add_sep(void) // Add a separator to current menu
{
pt_menuitem mi;
pt_menu m;
m = (ms->menus[ms->nummenus - 1]);
mi = NULL;
mi = (pt_menuitem) malloc(sizeof(t_menuitem));
if (mi == NULL)
return NULL;
m->items[(unsigned int)m->numitems] = mi;
mi->handler = NULL; // No handler
mi->item = mi->status = mi->data = NULL;
mi->action = OPT_SEP;
mi->index = m->numitems++;
mi->parindex = ms->nummenus - 1;
mi->shortcut = 0;
mi->helpid = 0;
return mi;
}
// Add item to the "current" menu
pt_menuitem add_item(const char *item, const char *status, t_action action,
const char *data, uchar itemdata)
{
pt_menuitem mi;
pt_menu m;
const char *str;
uchar inhlite = 0; // Are we inside hlite area
m = (ms->menus[ms->nummenus - 1]);
mi = NULL;
mi = (pt_menuitem) malloc(sizeof(t_menuitem));
if (mi == NULL)
return NULL;
m->items[(unsigned int)m->numitems] = mi;
mi->handler = NULL; // No handler
// Allocate space to store stuff
mi->item = (char *)malloc(MENULEN + 1);
mi->status = (char *)malloc(STATLEN + 1);
mi->data = (char *)malloc(ACTIONLEN + 1);
if (item) {
if (strlen(item) > MENULEN) {
strcpy(mi->item, ITEMLONG);
} else {
strcpy(mi->item, item);
}
if (strlen(mi->item) > m->menuwidth)
m->menuwidth = strlen(mi->item);
} else
strcpy(mi->item, EMPTYSTR);
if (status) {
if (strlen(status) > STATLEN) {
strcpy(mi->status, STATUSLONG);
} else {
strcpy(mi->status, status);
}
} else
strcpy(mi->status, EMPTYSTR);
mi->action = action;
str = mi->item;
mi->shortcut = 0;
mi->helpid = 0xFFFF;
inhlite = 0; // We have not yet seen an ENABLEHLITE char
// Find the first char in [A-Za-z0-9] after ENABLEHLITE and not arg to control char
while (*str) {
if (*str == ENABLEHLITE) {
inhlite = 1;
}
if (*str == DISABLEHLITE) {
inhlite = 0;
}
if ((inhlite == 1) &&
(((*str >= 'A') && (*str <= 'Z')) ||
((*str >= 'a') && (*str <= 'z')) ||
((*str >= '0') && (*str <= '9')))) {
mi->shortcut = *str;
break;
}
++str;
}
if ((mi->shortcut >= 'A') && (mi->shortcut <= 'Z')) // Make lower case
mi->shortcut = mi->shortcut - 'A' + 'a';
if (data) {
if (strlen(data) > ACTIONLEN) {
strcpy(mi->data, ACTIONLONG);
} else {
strcpy(mi->data, data);
}
} else
strcpy(mi->data, EMPTYSTR);
switch (action) {
case OPT_SUBMENU:
mi->itemdata.submenunum = itemdata;
break;
case OPT_CHECKBOX:
mi->itemdata.checked = itemdata;
break;
case OPT_RADIOMENU:
mi->itemdata.radiomenunum = itemdata;
if (mi->data)
free(mi->data);
mi->data = NULL; // No selection made
break;
default: // to keep the compiler happy
break;
}
mi->index = m->numitems++;
mi->parindex = ms->nummenus - 1;
return mi;
}
// Set the shortcut key for the current item
void set_item_options(uchar shortcut, int helpid)
{
pt_menuitem mi;
pt_menu m;
m = (ms->menus[ms->nummenus - 1]);
if (m->numitems <= 0)
return;
mi = m->items[(unsigned int)m->numitems - 1];
if (shortcut != 0xFF)
mi->shortcut = shortcut;
if (helpid != 0xFFFF)
mi->helpid = helpid;
}
// Free internal datasutructures
void close_menusystem(void)
{
}
// append_line_helper(pt_menu menu,char *line)
void append_line_helper(int menunum, char *line)
{
pt_menu menu;
pt_menuitem mi, ri;
char *app;
int ctr;
menu = ms->menus[menunum];
for (ctr = 0; ctr < (int)menu->numitems; ctr++) {
mi = menu->items[ctr];
app = NULL; //What to append
switch (mi->action) {
case OPT_CHECKBOX:
if (mi->itemdata.checked)
app = mi->data;
break;
case OPT_RADIOMENU:
if (mi->data) { // Some selection has been made
ri = (pt_menuitem) (mi->data);
app = ri->data;
}
break;
case OPT_SUBMENU:
append_line_helper(mi->itemdata.submenunum, line);
break;
default:
break;
}
if (app) {
strcat(line, " ");
strcat(line, app);
}
}
}
// Generate string based on state of checkboxes and radioitem in given menu
// Assume line points to large enough buffer
void gen_append_line(const char *menu_name, char *line)
{
int menunum;
menunum = find_menu_num(menu_name);
if (menunum < 0)
return; // No such menu
append_line_helper(menunum, line);
}