/* -*- c -*- ------------------------------------------------------------- *
 *
 *   Copyright 2004 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.
 *
 * ----------------------------------------------------------------------- */

/* This program can be compiled for DOS with the OpenWatcom compiler
 * (http://www.openwatcom.org/):
 *
 * wcl -3 -osx -mt getargs.c
 */

#include "biosio.h"
#include "string.h"
#include "menu.h"
#include "heap.h"

// Local Variables
static pt_menusystem ms; // Pointer to the menusystem
static char TITLESTR[] = "COMBOOT Menu System for SYSLINUX developed by Murali Krishnan Ganapathy";
static char TITLELONG[] = " TITLE too long ";
static char ITEMLONG[] = " ITEM too long ";
static char ACTIONLONG[] = " ACTION too long ";
static char STATUSLONG[] = " STATUS too long ";
static char EMPTYSTR[] = "";

/* 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.
char getch(char *scan)
{
  unsigned long i;
  TIMEOUTCODE c;

  // Wait until keypress if no handler specified
  if (ms->ontimeout==NULL) return inputc(scan);

  while (1) // Forever do
    {
      for (i=0; i < ms->tm_numsteps; i++)
	{
	  if (checkkbdbuf()) return inputc(scan);
	  sleep(ms->tm_stepsize);
	}
      c = ms->ontimeout();
      switch(c)
	{
	case CODE_ENTER: // Pretend user hit enter
	  *scan = ENTERA;
	  return '\015'; // \015 octal = 13
	case CODE_ESCAPE: // Pretend user hit escape
	  *scan = ESCAPE;
	  return '\033'; // \033 octal = 27
	default:
	  break;
	}
    }
  return 0;
}

/* Print a menu item */
/* attr[0] is non-hilite attr, attr[1] is highlight attr */
void printmenuitem(const char *str,char* attr)
{
    char page = getdisppage();
    char row,col;
    int hlite=NOHLITE; // Initially no highlighting

    getpos(&row,&col,page);
    while ( *str ) {
      switch (*str) 
	{
	case '\b':
	  --col;
	  break;
	case '\n':
	  ++row;
	  break;
	case '\r':
	  col=0;
	  break;
	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], page);
	  ++col;
	}
      if (col > getnumcols())
	{
	  ++row;
	  col=0;
	}
      if (row > getnumrows())
	{
	  scrollup();
	  row= getnumrows();
	}
      gotoxy(row,col,page);
      str++;
    }
}

void drawbox(char top, char left, char bot, char right,char attr, char page)
{
  char x;
    
  // Top border
  gotoxy(top,left,page);
  cprint(TOPLEFT,attr,1,page);
  gotoxy(top,left+1,page);
  cprint(TOP,attr,right-left,page);
  gotoxy(top,right,page);
  cprint(TOPRIGHT,attr,1,page);
  // Bottom border
  gotoxy(bot,left,page);
  cprint(BOTLEFT,attr,1,page);
  gotoxy(bot,left+1,page);
  cprint(BOT,attr,right-left,page);
  gotoxy(bot,right,page);
  cprint(BOTRIGHT,attr,1,page);
  // Left & right borders
  for (x=top+1; x < bot; x++)
    {
      gotoxy(x,left,page);
      cprint(LEFT,attr,1,page);
      gotoxy(x,right,page);
      cprint(RIGHT,attr,1,page);
    }
}

int next_visible(pt_menu menu, int index) // Return index of next visible
{
  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 next 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 find_shortcut(pt_menu menu,char 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
}

void printmenu(pt_menu menu, int curr, char top, char left)
{
  int x,row; // x = index, row = position from top
  int numitems,menuwidth;
  char fchar[5],lchar[5]; // The first and last char in for each entry
  const char *str;  // and inbetween the item or a seperator is printed
  char *attr;  // attribute attr
  char sep[MENULEN];// and inbetween the item or a seperator is printed
  pt_menuitem ci;
  
  calc_visible(menu);
  numitems = menu->numvisible;
  menuwidth = menu->menuwidth+3;
  clearwindow(top,left-2, top+numitems+1, left+menuwidth+1,
	      ms->menupage, ms->fillchar, ms->shadowattr);
  drawbox(top-1, left-3, top+numitems, left+menuwidth, 
	  ms->normalattr[NOHLITE], ms->menupage);
  memset(sep,HORIZ,menuwidth); // String containing the seperator string
  sep[menuwidth-1] = 0; 
  // Menu title
  x = (menuwidth - strlen(menu->title) - 1) >> 1;
  gotoxy(top-1,left+x,ms->menupage);
  printmenuitem(menu->title,ms->normalattr);
  row = -1; // 1 less than inital value of x
  for (x=0; x < menu->numitems; x++)
    {
      ci = menu->items[x];
      if (ci->action == OPT_INVISIBLE) continue;
      row++;
      // Setup the defaults now
      lchar[0] = fchar[0] = ' '; 
      lchar[1] = fchar[1] = '\0'; // fchar and lchar are just spaces
      str = ci->item; // Pointer to item string
      attr = (x==curr ? ms->reverseattr : ms->normalattr); // Normal attributes
      switch (ci->action) // set up attr,str,fchar,lchar for everything
        {
	case OPT_INACTIVE:
	  attr = (x==curr? ms->revinactattr : ms->inactattr);
	  break;
	case OPT_SUBMENU:
	  lchar[0] = SUBMENUCHAR; lchar[1] = 0;
	  break;
	case OPT_RADIOMENU:
	  lchar[0] = RADIOMENUCHAR; lchar[1] = 0;
	  break;
	case OPT_CHECKBOX:
	  lchar[0] = (ci->itemdata.checked ? CHECKED : UNCHECKED);
	  lchar[1] = 0;
	  break;
	case OPT_SEP:
	  fchar[0] = '\b'; fchar[1] = LTRT; fchar[2] = HORIZ; fchar[3] = HORIZ; fchar[4] = 0;
	  lchar[0] = HORIZ; lchar[1] = RTLT; lchar[2] = 0;
	  str = sep;
	  break;
	case OPT_EXITMENU:
	  fchar[0] = EXITMENUCHAR; fchar[1] = 0;
	  break;
	default: // Just to keep the compiler happy
	  break;
        }
      gotoxy(top+row,left-2,ms->menupage);
      cprint(ms->spacechar,attr[NOHLITE],menuwidth+2,ms->menupage); // Wipe area with spaces
      gotoxy(top+row,left-2,ms->menupage);
      csprint(fchar,attr[NOHLITE]); // Print first part
      gotoxy(top+row,left,ms->menupage);
      printmenuitem(str,attr); // Print main part
      gotoxy(top+row,left+menuwidth-1,ms->menupage); // Last char if any
      csprint(lchar,attr[NOHLITE]); // Print last part
    }
  if (ms->handler) ms->handler(ms,menu->items[curr]);
}

// Difference between this and regular menu, is that only 
// OPT_INVISIBLE, OPT_SEP are honoured 
void printradiomenu(pt_menu menu, int curr, char top, char left)
{
  int x,row; // x = index, row = position from top
  int numitems,menuwidth;
  char fchar[5],lchar[5]; // The first and last char in for each entry
  const char *str;  // and inbetween the item or a seperator is printed
  char *attr;  // all in the attribute attr
  char sep[MENULEN];// and inbetween the item or a seperator is printed
  pt_menuitem ci;
  
  calc_visible(menu);
  numitems = menu->numvisible;
  menuwidth = menu->menuwidth+3;
  clearwindow(top,left-2, top+numitems+1, left+menuwidth+1,
	      ms->menupage, ms->fillchar, ms->shadowattr);
  drawbox(top-1, left-3, top+numitems, left+menuwidth, 
	  ms->normalattr[NOHLITE], ms->menupage);
  memset(sep,HORIZ,menuwidth); // String containing the seperator string
  sep[menuwidth-1] = 0; 
  // Menu title
  x = (menuwidth - strlen(menu->title) - 1) >> 1;
  gotoxy(top-1,left+x,ms->menupage);
  printmenuitem(menu->title,ms->normalattr);
  row = -1; // 1 less than inital value of x
  for (x=0; x < menu->numitems; x++)
    {
      ci = menu->items[x];
      if (ci->action == OPT_INVISIBLE) continue;
      row++;
      // Setup the defaults now
      fchar[0] = RADIOUNSEL; fchar[1]='\0'; // Unselected ( )
      lchar[0] = '\0'; // Nothing special after 
      str = ci->item; // Pointer to item string
      attr = ms->normalattr; // Always same attribute
      fchar[0] = (x==curr ? RADIOSEL : RADIOUNSEL); 
      switch (ci->action) // set up attr,str,fchar,lchar for everything
        {
	case OPT_INACTIVE:
	  attr = ms->inactattr;
	  break;
	case OPT_SEP:
	  fchar[0] = '\b'; fchar[1] = LTRT; fchar[2] = HORIZ; fchar[3] = HORIZ; fchar[4] = 0;
	  lchar[0] = HORIZ; lchar[1] = RTLT; lchar[3] = 0;
	  str = sep;
	  break;
	default: // To keep the compiler happy
	  break;
        }
      gotoxy(top+row,left-2,ms->menupage);
      cprint(ms->spacechar,attr[NOHLITE],menuwidth+2,ms->menupage); // Wipe area with spaces
      gotoxy(top+row,left-2,ms->menupage);
      csprint(fchar,attr[NOHLITE]); // Print first part
      gotoxy(top+row,left,ms->menupage);
      printmenuitem(str,attr); // Print main part
      gotoxy(top+row,left+menuwidth-1,ms->menupage); // Last char if any
      csprint(lchar,attr[NOHLITE]); // Print last part
    }
  if (ms->handler) ms->handler(ms,menu->items[curr]);
}

void cleanupmenu(pt_menu menu, char top,char left)
{
  clearwindow(top,left-2, top+menu->numvisible+1, left+menu->menuwidth+4,
	      ms->menupage, ms->fillchar, ms->fillattr); // Clear the shadow
  clearwindow(top-1, left-3, top+menu->numvisible, left+menu->menuwidth+3,
	      ms->menupage, ms->fillchar, ms->fillattr); // main window
}

/* Handle a radio menu */
pt_menuitem getradiooption(pt_menu menu, char top, char left, char startopt)
     // Return item chosen or NULL if ESC was hit.
{
  int curr,i;
  char asc,scan;
  char numitems;
  pt_menuitem ci; // Current item
    
  calc_visible(menu);
  numitems = menu->numvisible;
  // Setup status line
  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);

  // Initialise current menu item
  curr = next_visible(menu,startopt);

  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,1);
  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  printmenuitem(menu->items[curr]->status,ms->statusattr);
  while (1) // Forever
    {
      printradiomenu(menu,curr,top,left);
      ci = menu->items[curr];
      
      asc = getch(&scan);
      switch (scan)
        {
	case HOMEKEY:
	  curr = next_visible(menu,0);
	  break;
	case ENDKEY:
	  curr = prev_visible(menu,numitems-1);
	  break;
	case PAGEDN:
	  for (i=0; i < 5; i++) curr = next_visible(menu,curr+1);
	  break;
	case PAGEUP:
	  for (i=0; i < 5; i++) curr = prev_visible(menu,curr-1);
	  break;
	case UPARROW:
	  curr = prev_visible(menu,curr-1);
	  break;
	case DNARROW:
	  curr = next_visible(menu,curr+1);
	  break;
	case LTARROW:
	case ESCAPE:
	  return NULL;
	  break;
	case RTARROW:
	case ENTERA:
	case ENTERB:
	  if (ci->action == OPT_INACTIVE) break;
	  if (ci->action == OPT_SEP) break;
	  return ci;
	  break;
	default:
	  // Check if this is a shortcut key
	  if (((asc >= 'A') && (asc <= 'Z')) ||
	      ((asc >= 'a') && (asc <= 'z')) ||
	      ((asc >= '0') && (asc <= '9')))
	    curr = find_shortcut(menu,asc,curr);
	  break;
        }
      // Update status line
      gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
      cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
      printmenuitem(menu->items[curr]->status,ms->statusattr);
    }
  return NULL; // Should never come here
}

/* Handle one menu */
pt_menuitem getmenuoption( pt_menu menu, char top, char left, char startopt)
     // Return item chosen or NULL if ESC was hit.
{
  int curr,i;
  char asc,scan;
  char numitems;
  pt_menuitem ci; // Current item
    
  calc_visible(menu);
  numitems = menu->numvisible;
  // Setup status line
  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);

  // Initialise current menu item    
  curr = next_visible(menu,startopt);

  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,1);
  gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
  printmenuitem(menu->items[curr]->status,ms->statusattr);
  while (1) // Forever
    {
      printmenu(menu,curr,top,left);
      ci = menu->items[curr];
      asc = getch(&scan);
      switch (scan)
        {
	case HOMEKEY:
	  curr = next_visible(menu,0);
	  break;
	case ENDKEY:
	  curr = prev_visible(menu,numitems-1);
	  break;
	case PAGEDN:
	  for (i=0; i < 5; i++) curr = next_visible(menu,curr+1);
	  break;
	case PAGEUP:
	  for (i=0; i < 5; i++) curr = prev_visible(menu,curr-1);
	  break;
	case UPARROW:
	  curr = prev_visible(menu,curr-1);
	  break;
	case DNARROW:
	  curr = next_visible(menu,curr+1);
	  break;
	case LTARROW:
	case ESCAPE:
	  return NULL;
	  break;
	case RTARROW:
	case ENTERA:
	case ENTERB:
	  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
	  return ci;
	  break;
	case SPACEKEY:
	  if (ci->action != OPT_CHECKBOX) break;
	  ci->itemdata.checked = !ci->itemdata.checked;
	  // Call handler to see it anything needs to be done
	  if (ci->handler != NULL) ci->handler(ms,ci); 
	  break;
	default:
	  // Check if this is a shortcut key
	  if (((asc >= 'A') && (asc <= 'Z')) ||
	      ((asc >= 'a') && (asc <= 'z')) ||
	      ((asc >= '0') && (asc <= '9')))
	    curr = find_shortcut(menu,asc,curr);
	  break;
        }
      // Update status line
      gotoxy(ms->minrow+ms->statline,ms->mincol,ms->menupage);
      cprint(ms->spacechar,ms->statusattr[NOHLITE],ms->numcols,ms->menupage);
      printmenuitem(menu->items[curr]->status,ms->statusattr);
    }
  return NULL; // Should never come here
}

/* Handle the entire system of menu's. */
pt_menuitem runmenusystem(char top, char left, pt_menu cmenu, char startopt, char 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;
  char startat,mt;
  char row,col;

  if (cmenu == NULL) return NULL;
 startover:
  if (menutype == NORMALMENU)
    opt = getmenuoption(cmenu,top,left,startopt);
  else // menutype == RADIOMENU
    opt = getradiooption(cmenu,top,left,startopt);

  if (opt == NULL)
    {
      // User hit Esc
      cleanupmenu(cmenu,top,left);
      return NULL;
    }
  // Are we done with the menu system?
  if ((opt->action != OPT_SUBMENU) && (opt->action != OPT_RADIOMENU)) 
    {
      cleanupmenu(cmenu,top,left);
      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,ms->menupage); // Middle of screen
      csprint("ERROR: Invalid submenu requested.",0x07);
      cleanupmenu(cmenu,top,left);
      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); // Call handler
      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);
      return choice;
    }
}

/* User Callable functions */

pt_menuitem showmenus(char startmenu)
{
  pt_menuitem rv;
  char oldpage,tpos;

  // Setup screen for menusystem
  oldpage = getdisppage();
  setdisppage(ms->menupage);
  cls();
  clearwindow(ms->minrow, ms->mincol, ms->maxrow, ms->maxcol, 
	      ms->menupage, ms->fillchar, ms->fillattr);
  tpos = (ms->numcols - strlen(ms->title) - 1) >> 1; // center it on line    
  gotoxy(ms->minrow,ms->mincol,ms->menupage);
  cprint(ms->tfillchar,ms->titleattr,ms->numcols,ms->menupage);
  gotoxy(ms->minrow,ms->mincol+tpos,ms->menupage);
  csprint(ms->title,ms->titleattr);

  cursoroff(); // Doesn't seem to work?

  // 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
  cursoron();
  if (oldpage == ms->menupage) cls(); else setdisppage(oldpage);

  // Return user choice
  return rv;
}

void init_menusystem(const char *title)
{
  int i;
    
  ms = NULL;
  ms = (pt_menusystem) malloc(sizeof(t_menusystem));
  if (ms == NULL) return;
  ms->nummenus = 0;
  // Initialise all menu pointers
  for (i=0; i < MAXMENUS; i++) ms->menus[i] = NULL; 
    
  if (title == NULL)
    ms->title = TITLESTR; // Copy pointers
  else 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
  ms->handler = NULL; // No handler function
  ms->ontimeout=NULL; // No timeout handler

  // Figure out the size of the screen we are in now.
  // By default we use the whole screen for our menu
  ms->minrow = ms->mincol = 0;
  ms->numcols = getnumcols();
  ms->numrows = getnumrows();
  ms->maxcol = ms->numcols - 1;
  ms->maxrow = ms->numrows - 1;
}

void set_normal_attr(char normal, char selected, char inactivenormal, char 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(char normal, char selected, char inactivenormal, char 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(char statusattr, char statushlite, char 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(char tfillchar, char titleattr)
{
  if (tfillchar  != 0xFF) ms->tfillchar  = tfillchar;
  if (titleattr  != 0xFF) ms->titleattr  = titleattr;
}

void set_misc_info(char fillchar, char fillattr,char spacechar, char 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_window_size(char top, char left, char bot, char right) // Set the window which menusystem should use
{
    
  char nr,nc;
  if ((top > bot) || (left > right)) return; // Sorry no change will happen here
  nr = getnumrows();
  nc = getnumcols();
  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_menusystem_handler handler)
{
  ms->handler = handler;
}

void unreg_handler()
{
  ms->handler = NULL;
}

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()
{
  ms->ontimeout = NULL;
}


void calc_visible(pt_menu menu)
{
  int ans,i;

  if (menu == NULL) return;  
  ans = 0;
  for (i=0; i < menu->numitems; i++)
    if (menu->items[i]->action != OPT_INVISIBLE) ans++;
  menu->numvisible = ans;
}

char add_menu(const char *title) // Create a new menu and return its position
{
  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->row = 0xFF;
  m->col = 0xFF;
  for (i=0; i < MAXMENUSIZE; i++) m->items[i] = NULL;
   
  if (title)
    {
      if (strlen(title) > MENULEN - 2)
	m->title = TITLELONG;
      else m->title = title; 
    }
  else m->title = EMPTYSTR; 
  m ->menuwidth = strlen(m->title);
  ms->nummenus ++;
  return ms->nummenus - 1;
}

void set_menu_pos(char row,char 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() // 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 = EMPTYSTR;
  mi->action = OPT_SEP;
  mi->index = m->numitems++;
  mi->parindex = ms->nummenus-1;
  mi->shortcut = 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, char itemdata) 
{
  pt_menuitem mi;
  pt_menu m;
  const char *str;
  char 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
  if (item) {
    if (strlen(item) > MENULEN - 2) {
      mi->item = ITEMLONG; 
    } else {
      mi->item = item; 
      if (strlen(item) > m->menuwidth) m->menuwidth = strlen(item);
    }
  } else mi->item = EMPTYSTR; 

  if (status) {
    if (strlen(status) > STATLEN - 2) {
      mi->status = STATUSLONG; 
    } else {
      mi->status = status; 
    }
  } else mi->status = EMPTYSTR; 
    
  mi->action = action;
  str = mi->item;
  mi->shortcut = 0;
  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 - 2) {
      mi->data = ACTIONLONG; 
    } else {
      mi->data = data; 
    }
  } else 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;
      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_shortcut(char shortcut)
{
  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];
  mi->shortcut = shortcut;
}
