/*
****************************************************************************

 ##   ##   ##     ####   ##   ##                        
 ### ###  ####   ##  ##  ### ###                        
 ####### ##  ##  ##      #######  ####   #####   ##  ## 
 ## # ## ##  ##  ##      ## # ## ##  ##  ##  ##  ##  ## 
 ##   ## ######  ##      ##   ## ######  ##  ##  ##  ## 
 ##   ## ##  ##  ##  ##  ##   ## ##      ##  ##  ##  ## 
 ##   ## ##  ##   ####   ##   ##  ####   ##  ##   ### ##
                                                        
****************************************************************************

 Module:      MACMenu.c

 Description: A general purpose, full screen,  character based menuing 
              system, for for use by other programs as a user interface.
              This version of MACMenu is ncurses based, for use with Linux
              and unix type operating systems.

 Version:     2.5a1 (SuSE Linux 9.0, gcc compiler )

 Author:      Michael Campbell

****************************************************************************
*/

/**/

#include <stdio.h>
#include <string.h>
#include <curses.h>

extern void exit ();

/*
---------------------------------------------------
Private Constants
---------------------------------------------------
*/

#include "macmenu.def"
#include "loantype.def"

/* Note that macmenu.def defines public constants that users need        */

/* Define the maximum number of menus, and the maximum number of menu    */
/* items within a single menu.                                           */

#define MAX_MENUS       16
#define MAX_ITEMS       20

/* The following are the X offsets for each of the fields of a menu item */

#define ITEM_SELECT_X    2
#define ITEM_DESC_X      6
#define ITEM_DATA_X     58

/* The following define the types of MACMenu generated lines              */

#define LT_NIL            0
#define LT_TITLE          1
#define LT_STATUS         2
#define LT_COMMAND        3

/* Cursor visibility states */

#define CURS_VISIBLE      1
#define CURS_INVISIBLE    0

/**/

/*
---------------------------------------------------
Private Types
---------------------------------------------------
*/

/* Note that macmenu.def defines public types that users need */

/* Definition of one element in a menu */

struct t_menu_item {
   char  select_key;                    /* Command key for this item       */
   int   alias_num;                     /* Num defined aliases for the key */
   char  select_alias [4];              /* Aliases for the select key      */
   char  description [50];              /* Descriptive text for this item  */
   int   y_coord;                       /* Y co-ordinate of this item      */
   int   data_type;                     /* Type of data stored in item_data*/
   char  *data_ptr;                     /* Pointer to data to display      */
};


/* Master type definition for a menu */

struct t_menu {
   BOOL  allocated;                 /* Is menu descriptor allocated?       */
   int   num_items;                 /* Number of menu items                */
   BOOL  item_overflow;             /* More items added than space for?    */
   int   vertical_offset;           /* # of lines to offset menu by        */
   int   case_sensitive;            /* Are selections case sensitive?      */
   int   border_color;              /* Color of screen border              */
   short menu_color_pair;           /* ncurses color pair for menu         */
   short fg_color;                  /* Color of menu line text             */
   short bg_color;                  /* Color of menu line background       */
   short sys_color_pair;            /* ncurses color pair for sys area     */
   short sys_fg;                    /* Color of cmd/status line text       */
   short sys_bg;                    /* Color of cmd/status line background */
   char  menu_title [82];           /* The displayed title of the menu     */
   struct t_menu_item menu_items [MAX_ITEMS];  /* Array of menu items      */
};

/**/

/*
----------------------------------------------------
Private, module-global variables
----------------------------------------------------
*/

static struct t_menu    menus [MAX_MENUS]; /* The set of menu descriptors  */

static int  screenwidth;                   /* The width of the init screen */
static int  screenheight;                  /* The height of the init screen*/

static char *command_prompt = "Command        : ";
static char *status_prompt  = "Prompt/Response: ";

static int  cp_len, sp_len;                /* Lengths of above two prompts */
static int  max_status_len;                /* Maximum status string len    */

static int  curr_menu;                     /* Currently displayed menu     */

/* The following are the x and y co-ordinates of the status and command   */
/* lines.                                                                 */

int COMMAND_X =  1;
int COMMAND_Y = 22;
int STATUS_X  =  1;
int STATUS_Y  = 23;

/*
----------------------------------------------------
Public variables
----------------------------------------------------
*/

WINDOW *lm_win; /* The main ncurses window  */
WINDOW *pu_win; /* The current popup window */

/**/

/*
----------------------------------------------------
Private Functions
----------------------------------------------------
*/

static void put_item_data ( x, y, data_type, data_ptr )    

/****************************************************************************
 This function formats and prints the optional data that may be part of
 a menu item. The cursor is moved to the indicated x and y co-ordinates
 before the data is written. Any previous data at that location is cleared.

 This function returns no value.
 ***************************************************************************/

 int  x, y;                       /* Item data position                    */
 int  data_type;                  /* The type of the data to be output     */
 char *data_ptr;                  /* Pointer to the data itself            */

{
   int avail_len;                 /* Length of available display space     */
   int data_len;                  /* Length of the data to output          */
   char *string_ptr;              /* Pointer to a string to output         */

   /* Clear any existing data at the indicated location.                   */

   wmove ( lm_win, y, x );
   wclrtoeol ( lm_win );;

   /* Now output the new data item                                         */

   switch ( data_type ) {

      case D_STRING:

         /* MACMenu was originally written for DOS and 80 col windows.     */
         /* ALL of the data placement is set for 80 cols. In the linux     */
         /* port, this isn't disturbed. Irrespective of screenwidth, use   */
         /* 80 (instead of screenwidth) to position string output, so that */
         /* visual alignment with everything else is maintained.           */

         avail_len = 80 - x - 2;
         data_len  = strlen ( data_ptr );

         if ( data_len <= avail_len ) {

            /* If the length of the data to display is less than the avail */
            /* display space, simply modify x so the displayed string will */
            /* be right justified.                                         */

            x = 80 - data_len - 2;
            string_ptr = data_ptr;
         }
         else {
            /* The data to be displayed will not fit in the available      */
            /* display space. Modify the string pointer to point into the  */
            /* string at an offset such that the remainder will fit.       */

            string_ptr = &data_ptr[data_len - avail_len + 1];

            /* Output a truncation mark */

            mvwaddch  ( lm_win, y, x, '<' );
            x += 1;
         }

         mvwaddstr ( lm_win, y, x, string_ptr );
         break;

      case D_INT:
         wprintw ( lm_win, "%20d",   *((int *)data_ptr) );
         break;

      case D_FLOAT:
         wprintw ( lm_win, "%20.2f", *((float *)data_ptr) );
         break;

      case D_DOUBLE:
         wprintw ( lm_win, "%20.2f", *((double *)data_ptr) );
         break;

      case D_NONE:
         break;

      default:
         waddstr ( lm_win, "Data Error!" );
         break;
   }

} /* put_item_data */

/**/

static void set_line_color ( pair_num, line_y, fg_color, bg_color )

/***************************************************************************
 This function sets the video attributes for the indicated line such that
 the entire displayed line has the indicated colors.

 This function does not return any value.
 ***************************************************************************/

 short pair_num;             /* The ncurses color pair to use              */
 int line_y;                 /* The Y co-ordinate of the line to set       */
 short fg_color;             /* The requested ncurses foreground color     */
 short bg_color;             /* The requested ncurses backgrond color      */

{
   register int i;           /* Loop counter                               */
   char attr_str[256];       /* Attribute string                           */

   /* Init a new ncurses color pair with the supplied equivalents          */

   init_pair ( pair_num, fg_color, bg_color );

   /* Enable a color attribute based on the new pair for the line          */

   wattron ( lm_win, COLOR_PAIR(pair_num) );

   for ( i = 0; i < screenwidth; i++ )
      attr_str[i] = ' ';
   attr_str[screenwidth] = '\0';

   mvwaddstr ( lm_win, line_y, 0, attr_str );

   /* Turn off the attribute now that we have assigned it to the line      */

   wattroff ( lm_win, COLOR_PAIR(pair_num));

} /* set_line_color */

/**/

static void clr_sys_line ( line_type )

/***************************************************************************
 This function clears the indicated system line by writing blanks to it.
 ***************************************************************************/

int line_type;             /* The type of the system line to clear         */

{
    register int i;        /* Simple loop counter                          */
    int line_x;            /* X offset before starting clear               */
    int line_y;            /* Y offset of the line                         */
    struct t_menu *menu;   /* Pointer to the current menu                  */
    char attr_str[256];    /* Attribute string                             */

    menu = &menus[curr_menu];

    /* Based on the line type, set the X and Y co-ordinates                */

    switch ( line_type ) {

       case LT_STATUS :
          line_x = sp_len;
          line_y = STATUS_Y;
          break;

       case LT_COMMAND :
          line_x = cp_len;
          line_y = COMMAND_Y;
          break;

       default :
          return;
    }

    /* Clear the line                                                      */

    for ( i = 0; i < screenwidth; i++ )
       attr_str[i] = ' ';
    attr_str[screenwidth-line_x] = '\0';

    wattron   ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
    mvwaddstr ( lm_win, line_y, line_x, attr_str       );

    wmove ( lm_win, line_y, line_x );

} /* clr_sys_line */

/**/

/*
---------------------------------------------------
Public Functions
---------------------------------------------------
*/

void create_macmenu_envt ( void )

/*****************************************************************************
 This function creates the MACMenu ncurses environment.
 *****************************************************************************/
{

   /* Initialize ncurses - echoing and signal trapping are left on         */

   lm_win = initscr ();     /* Start ncurses environment                   */
   start_color ();          /* Enable use of color in the environment      */
   keypad ( stdscr, TRUE ); /* Enable ncurses mapping of PC keyboard keys  */

   screenwidth  = COLS;
   screenheight = LINES;

   COMMAND_Y    = screenheight - 2;
   STATUS_Y     = screenheight - 1;

} /* create_macmenu_envt */


void remove_macmenu_envt ( void )

/*****************************************************************************
 This function removes MACMenu ncurses envirnoment.
 *****************************************************************************/

{
   endwin ();  /* remove the ncurses environment */

} /* remove_macmenu_envt */


unsigned char init_macmenu ( void )

/*****************************************************************************
 This function initializes the MACMenu data structures.
 This function returns 0
 *****************************************************************************/
{
   int i, j, k;               /* Loop counters                      */
   struct t_menu *menu;       /* Pointer to a menu descriptor       */
   struct t_menu_item *item;  /* Pointer to an item in a menu       */

   /* Initialize each menu descriptor */

   for ( i = 0; i < MAX_MENUS; i++) {

      menu = &menus[i];

      menu->allocated       = FALSE;
      menu->num_items       = 0;
      menu->item_overflow   = FALSE;
      menu->vertical_offset = 0;
      menu->case_sensitive  = FALSE;
      menu->border_color    = 0;
      menu->menu_color_pair = MENU_PAIR_NUM;
      menu->fg_color        = 0;
      menu->fg_color        = 0;
      menu->sys_color_pair  = SYS_LINES_PAIR_NUM;
      menu->sys_fg          = 0;
      menu->sys_bg          = 0;
      setmem ( &menu->menu_title, sizeof(menu->menu_title), (char)0x00 );

      /* Initialize each item in the menu */

      for ( j = 0; j < MAX_ITEMS; j++ ) {

         item = &menu->menu_items[j];

         item->select_key  = (char) 0x00;
         item->alias_num   = 0;
         item->y_coord     = 0;
         item->data_type   = D_NONE;
         item->data_ptr    = NULL;

         for ( k = 0; k < sizeof(item->select_alias); k++ )
            item->select_alias[k]    = (char) 0x00;

         setmem ( &item->description, sizeof(item->description), (char)0x00 );
      }
   }

   create_macmenu_envt ();  /* Start up ncurses */

   /* Finally, set up some globals, and clear the current menu indication  */

   cp_len         = strlen(command_prompt);
   sp_len         = strlen(status_prompt);
   max_status_len = screenwidth - sp_len;

   curr_menu = NIL_MENU_ID;

   return ( 0 );

} /* init_macmenu */

/**/

void exit_macmenu ( void )

/*****************************************************************************
 This function exits macmenu by restoring the text video attributes that were
 in effect when macmenu was initialized.

 This function takes no arguments, and returns no values.
 *****************************************************************************/

{

   remove_macmenu_envt ();  /* Close down ncurses */
    
} /* exit_macmenu */

/**/

int create_popup_window ( sx, sy, ul_x, ul_y, length, height, fg, bg )

/***************************************************************************
 *                                                                         *
 * This function creates a "pop up" window on screen, with the indicated   *
 * colors, and clears it. This function returns 0 if all goes well, or a   *
 * non-zero value, if it does not. The window is positioned such that its  *
 * upper left corner is placed at (ul_x, ul_y). If these values are set to *
 * to the constant W_CENTER, the window is centered on screen.             *
 *                                                                         *
 * This function loads the global variable pu_win with the token for the   *
 * created window.                                                         *
 *                                                                         *
 ***************************************************************************/

int   sx, sy;         /* The x and y maximums for the main window          */
int   ul_x, ul_y;     /* The upper left x and y co-ordinates for the wdw   */
int   length, height; /* The intended length and height of the new window  */
short fg, bg;         /* The foreground and background colors to use       */

{
    int result = E_OK;

    /* Center the window if so requested.                                  */

    if ( ul_x == W_CENTER && ul_y == W_CENTER ) {
       ul_y = ( sy - height ) / 2;
       ul_x = ( sx - length ) / 2;
    }

    /* Create the window */

    pu_win = newwin ( height, length, ul_y, ul_x );

    if ( pu_win == NULL ) 
       result = E_WINDOW_CREATE_FAILURE;

    else
    {
       /* Set the window to the requested colors and draw a box */
       /* around it.                                            */

       init_pair ( POPUP_PAIR_NUM, fg, bg );
       wbkgd ( pu_win, COLOR_PAIR(POPUP_PAIR_NUM) );

       wmove ( pu_win, 0, 0 );
       wclrtobot ( pu_win );

       wborder ( pu_win, 0, 0, 0, 0, 0, 0, 0, 0 );

       /* Finally, force the window to be displayed */

       wrefresh ( pu_win );
    }

    return ( result );

} /* create_popup_window */

/**/

void adorn_popup_window ( length, height, fg, bg, title, footer )

/***************************************************************************
 *                                                                         *
 * This function "adorns" a previously created popup window by adding the  *
 * header and footer lines. These are usually done in a different color,   *
 * to make the edges of the window standout.                               *
 *                                                                         *
 * This function operates on the window whose token is in the global       *
 * variable pu_win.                                                        *
 *                                                                         *
 ***************************************************************************/

int   length, height; /* The length and height of the window               */
short fg, bg;         /* The foreground and background colors to use       */
char  *title;         /* The title line                                    */
char  *footer;        /* The footer line (last line on screen)             */

{
   int hor_offset;

   init_pair ( POPUP_TITLE_PAIR_NUM, fg, bg );    /* Set up the colors     */
   wattron ( pu_win, COLOR_PAIR(POPUP_TITLE_PAIR_NUM) );

   hor_offset = ( length - strlen(title) ) / 2;   /* Do the title          */
   mvwaddstr ( pu_win, 0, hor_offset, title );

   hor_offset = ( length - strlen(footer) ) / 2;  /* Do the footer         */
   mvwaddstr ( pu_win, height-1, hor_offset, footer );

   wattroff ( pu_win, COLOR_PAIR(POPUP_TITLE_PAIR_NUM) );

   curs_set ( CURS_INVISIBLE );  /* Hide the cursor while popup displayed  */

   wrefresh ( pu_win );

} /* adorn_popup_window */

/**/

void destroy_popup_window ( sx, sy, menu )

/***************************************************************************
 *                                                                         *
 * This function destroys and existing popup window by setting the window  *
 * size back to the system console size, and displaying the indicated      *
 * menu.                                                                   *
 *                                                                         *
 * This function destroys the window whose token is in the global var      *
 * pu_win and redraws the window whose token is in the global var lm_win.  *
 *                                                                         *
 ***************************************************************************/

int     sx, sy;  /* The size of the physical system console                */
MENU_ID menu;    /* The menu to display                                    */

{
    delwin ( pu_win );        /* Delete the popup window                   */

    redrawwin ( lm_win );     /* Mark main window as needing to be redrawn */
    wrefresh  ( lm_win );     /* Redraw main windows                       */

    curs_set ( CURS_VISIBLE ); /* Redisplay the cursor                     */

} /* destroy_popup_window */

/**/

MENU_ID create_menu ( vertical_offset, title, case_sensitive, border_color,
                      sys_fg, sys_bg, fg_color, bg_color )

/*****************************************************************************
 This function allocates a menu descriptor, and initializes it with the 
 supplied data. It returns an ID for the newly created menu, which must
 be supplied to all other functions in this package that manipulate the menu.

 This function returns NIL_MENU_ID if no menu descriptors are available.
 *****************************************************************************/

int   vertical_offset; /* The color of the displayed text                    */
char  *title;          /* The title to display                               */
int   case_sensitive;  /* Are menu selections case sensitive?                */
int   border_color;    /* Are menu selections case sensitive?                */
short sys_fg;          /* The color of displayed text                        */
short sys_bg;          /* The color of the menu background                   */
short fg_color;        /* The color of displayed text                        */
short bg_color;        /* The color of the menu background                   */

{
   int i, j;                 /* Loop counters                              */
   struct t_menu *menu;      /* Pointer to the allocated menu              */
   MENU_ID menu_ix;          /* The returned menu ID                       */

   menu_ix = NIL_MENU_ID;

   /* Attempt to find a free menu descriptor */

   for ( i = 0; i < MAX_MENUS; i++ ) {

      if ( !menus[i].allocated ) {

         /* Found one! Mark it as allocated, and fill it in. */

         menu_ix = i;
         menu    = &menus[i];


         menu->allocated       = TRUE;
         menu->num_items       = 0;
         menu->vertical_offset = vertical_offset;
         menu->case_sensitive  = case_sensitive;
         menu->border_color    = border_color;

         init_pair ( MENU_PAIR_NUM, fg_color, bg_color );
         menu->menu_color_pair = MENU_PAIR_NUM;
         menu->fg_color        = fg_color;
         menu->bg_color        = bg_color;

         init_pair ( SYS_LINES_PAIR_NUM, sys_fg, sys_bg );
         menu->sys_color_pair  = SYS_LINES_PAIR_NUM;
         menu->sys_fg          = sys_fg;
         menu->sys_bg          = sys_bg;

         strcpy ( menu->menu_title, title );

         break;
      }
   }

   return ( menu_ix );

} /* create_menu */

/**/

void add_menu_item (selected_menu, select_key, description, data_type,
                    data_ptr )

/***************************************************************************
 This function adds a new item to an existing menu. An item is a visible
 line displayed on screen, consisting of a command key, descriptive text,
 and an optional data value to display. If a data value is supplied, the
 caller must also provide a data value type, so that macmenu knows how to
 format the data for display.

 This function does not return any value.
 ***************************************************************************/

   MENU_ID selected_menu;         /* The menu to which item is being added */
   char    select_key;            /* Command key for this item             */
   char    description [50];      /* Descriptive text for this item        */
   char    data_type;             /* Type of data stored in item_data      */
   char    *data_ptr;             /* Optional data value                   */
{
   struct t_menu *menu;           /* Pointer to the current menu           */
   struct t_menu_item *item;      /* Pointer to the new menu item          */
   int    i;                      /* Loop counter                          */

   menu = &menus[selected_menu];

   /* Check that no more than MAX_ITEMS exists for this menu.  If they do  */
   /* mark menu for future display.                                        */

   if ( menu->num_items != MAX_ITEMS ) {

      /* Copy over all fields to the item descriptor in the menu           */

      item = &menu->menu_items[menu->num_items++];

      /* For the select key for the item, we also load it as the first alias */
      /* Function get_menu_selection only scans the aliases at this point.   */

      item->select_key = (menu->case_sensitive ? select_key : toupper(select_key));
      item->alias_num       = 1;
      item->select_alias[0] = item->select_key;

      strcpy ( item->description, description );

      item->data_type = data_type;
      item->data_ptr  = data_ptr;
   }
   else
      menu->item_overflow = TRUE;

} /* add_menu_item */

/**/

int alias_select_key ( selected_menu, select_key, alias_key )

/***************************************************************************
 This function adds an alias to an existing select key for a menu item. 
 This allows MACMENU users to have up to four keys that return the same
 value. When the user enters an alias, MACMENU's get_menu_selection func
 treats it just as if the select key itself had been entered. The select
 key is what is returned. This allows, for example, use of both 'H' and 
 F1 for help, 'Q' and ESC for exit, and so on.

 This function returns 0 on success, and the following on failure:
   -1 : could not find the select key in the selected menu
   -2 : Too many aliases already defined
 ***************************************************************************/

MENU_ID selected_menu;          /* The menu to be used                     */
char    select_key;             /* The select key to be aliased            */
char    alias_key;              /* The alias being assigned                */

{
   struct t_menu      *menu;   /* The selected menu                        */
   struct t_menu_item *item;   /* An item in the selected menu             */
   int item_ix;                /* Index of the selected item in the menu   */
   int ret_code = -1;          /* Return codes                             */

   /* Find the selected item. If it cannot be found, return an error code */

   menu = &menus[selected_menu];

   for ( item_ix = 0; item_ix < MAX_ITEMS; item_ix++ ) {

      item = &menu->menu_items[item_ix];
      
      /* Scan for the supplied select key. If found, verify that there are */
      /* not a full set of aliases already. If there are, return an error  */
      /* code. If not, load up the alias.                                  */

      if ( item->select_key == select_key ) {

         if ( item->alias_num == sizeof(item->select_alias) ) {
            ret_code = -2;
         }
         else {
            item->select_alias[item->alias_num++] = alias_key;
            ret_code = 0;
         }

         break;
      }

   } /* for item_ix */

   return ( ret_code );

} /* alias_select_key */

/**/

void put_beep ( void )

/***************************************************************************
 This function puts out a short duration "pleasant" beep, to be used as a
 warning sound.
 ***************************************************************************/

{

   beep ();

}

/**/

void change_menu_title ( selected_menu, new_title )

/***************************************************************************
 This function changes the title of the indicated menu. If that menu is 
 the current menu, the menu title is redrawn.
 ***************************************************************************/

MENU_ID selected_menu;    /* The menu being updated                        */
char    *new_title;       /* The new title string                          */

{
   struct t_menu *menu;  /* Pointer to the current menu                    */
   int    hor_offset;    /* Spaces needed to center the title              */

   menu = &menus[selected_menu];

   strcpy ( menu->menu_title, new_title );

   if ( selected_menu == curr_menu ) {

      /* The menu we have just updated is the current displayed menu.     */
      /* Update the information displayed on screen.                      */

      set_line_color ( menu->sys_color_pair, 0, menu->sys_fg, menu->sys_bg );
      hor_offset = (screenwidth - strlen(menu->menu_title)) / 2;

      wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
      mvwaddstr ( lm_win, 0, hor_offset, menu->menu_title );
   }

} /* change_menu_title */

/**/

void put_menu ( selected_menu )

/***************************************************************************
 This function displays the indicated menu.

 This function does not return any value.
 ***************************************************************************/

   MENU_ID selected_menu;    /* The menu to display                       */

{
   int i, j;                 /* Loop counters                             */
   int cx, cy;               /* Cursor X and Y co-ordinates               */

   int hor_offset;           /* Offset of title within title line         */
   int vert_space;           /* Vertical spacing between menu items       */

   struct t_menu *menu;      /* Pointer to the current menu               */
   struct t_menu_item *item; /* Pointer to the current menu item          */

   menu = &menus[selected_menu];

   /* Clear the screen, and set the user supplied menu colors.            */
   /* Color pairs need to be re-init'd here because this may have been    */
   /* called after a shell out to the OS, which exits ncurses and then    */
   /* restarts it afterwards. Hence any existing color pairs may need to  */
   /* be re-init'd.                                                       */

   init_pair ( menu->menu_color_pair, menu->fg_color, menu->bg_color );
   init_pair ( menu->sys_color_pair,  menu->sys_fg,   menu->sys_bg   );

   werase ( lm_win );
   wbkgd  ( lm_win, COLOR_PAIR(menu->menu_color_pair) );

   /* Determine the vertical spacing between menu lines. This is done by  */
   /* computing the available vertical space (screen height minus asked   */
   /* for vertical offset) and dividing by the number of items in the     */
   /* menu. The resultant number is the required vertical space.          */

   vert_space = ( screenheight - 6 - menu->vertical_offset) / menu->num_items;
   vert_space = ( vert_space == 0 ? 1 : vert_space );

   /* Now start outputting the menu items, one at a time. */

   for ( i= 0, cy = menu->vertical_offset; i < menu->num_items; i++ ) {

      item          = &menu->menu_items[i];
      item->y_coord = cy;

      if ( item->select_key != (char) NIL_KEY ) {

         /* If this is not a nil entry (used to insert blank lines for     */
         /* spacing on screen), put out the command select key for this    */
         /* item, followed by a dash mark.                                 */

         mvwaddch  ( lm_win, cy, ITEM_SELECT_X, item->select_key  );
         mvwaddstr ( lm_win, cy, ITEM_SELECT_X + 1, " - "         );

         /* Put out the command description for this item                    */

         mvwaddstr ( lm_win, cy, ITEM_DESC_X, item->description );

         /* Put out any optional data (if present) */

         if ( item->data_type == D_STRING )
            cx = strlen(item->description) + ITEM_DESC_X + 5; /* 5 spaces  */
         else
            cx = ITEM_DATA_X;

         put_item_data ( cx, cy, item->data_type, item->data_ptr );
      }

      cy += vert_space;
   }

   /* Now that all menu items are out, put out the title, status and      */
   /* command lines.  Center the title of the menu in the first line of   */
   /* the screen. Display the title using the user sys colors.            */
   /* From here on in, the default colors are the user sys colors, since  */
   /* almost all reading and writing occurs on the sys lines. The data    */
   /* item write routines temporarily set the user menu colors for their  */
   /* operations, and then reset them to the sys colors, when done.       */

   set_line_color ( menu->sys_color_pair, 0,  menu->sys_fg, menu->sys_bg );
   hor_offset = (screenwidth - strlen(menu->menu_title)) / 2;

   wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
   mvwaddstr ( lm_win, 0, hor_offset, menu->menu_title );

   /* Put out the command line.                                            */

   set_line_color ( menu->sys_color_pair, COMMAND_Y, menu->sys_fg, menu->sys_bg );
   wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
   mvwaddstr ( lm_win, COMMAND_Y, COMMAND_X, command_prompt );

   /* Put out the status line. If there was an item overflow during menu  */
   /* definition, alert the user at this time.                            */

   set_line_color ( menu->sys_color_pair, STATUS_Y, menu->sys_fg, menu->sys_bg );
   wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
   mvwaddstr ( lm_win, STATUS_Y, STATUS_X, status_prompt );

   if ( menu->item_overflow )
      winsstr ( lm_win, "Too many selections defined for available space" );

   wattroff ( lm_win, COLOR_PAIR(menu->sys_color_pair) );

   /* Finally, update the current menu pointer, so that functions which    */
   /* update items that may be displayed (such as titles) can cause the    */
   /* item to be reshown, if it is presently displayed.                    */

   curr_menu = selected_menu;  

} /* put_menu */

/**/

void put_status_string ( status_string )

/***************************************************************************
 This function writes the supplied status_string onto the status line of the
 indicated menu.

 This function does not return any value.
 ***************************************************************************/

   char    *status_string;     /* Pointer to the string buffer to use     */

{
   struct t_menu *menu;       /* Pointer to the current menu              */

   menu = &menus[curr_menu];

   /* If the string to be put out is too long, cap it at its max length   */

   if ( strlen(status_string) > max_status_len ) {
      status_string[max_status_len - 1] = '\0';
      status_string[max_status_len - 2] = '>';
   }

   /* Move the cursor to the status line, and put out the message         */

   clr_sys_line ( LT_STATUS );
   wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
   mvwaddstr ( lm_win, STATUS_Y, STATUS_X + sp_len, status_string );

} /* put_status_string */

/**/

void put_menu_item_data ( selected_menu, select_key, data_type, data_ptr )

/*****************************************************************************
 This function displays a data value on an existing menu line. The caller
 must provide the menu_id, the command key for the line on which to display
 the data, an indication of the data type, and the data itself.

 This function does not return any value.
 *****************************************************************************/

   MENU_ID selected_menu;          /* The menu to which data is being written */
   char    select_key;             /* Command key for the target line         */
   char    data_type;              /* Type of data stored in item_data        */
   char    *data_ptr;              /* Pointer to the data to be written       */

{
   struct t_menu *menu;           /* Pointer to the selected menu         */
   struct t_menu_item *item;      /* Pointer to the relevant item         */
   int    item_ix;                /* Index of the current item            */
   int    x;                      /* X co-ordinate of stuff to display    */

   menu = &menus[selected_menu];

   /* Since we are writing to the user menu space, change back to the     */
   /* colors selected by the user.                                        */

   wattron ( lm_win, COLOR_PAIR(menu->menu_color_pair) );

   /* Find the selected item. If it cannot be found, output an error msg  */

   item_ix = 0;
   item    = NULL;

   for ( item_ix = 0; item_ix < MAX_ITEMS; item_ix++ ) {

      if ( menu->menu_items[item_ix].select_key == select_key ) {

         /* Found the desired menu item. Put the data into the item, and */
         /* then output the data                                         */

         item = &menu->menu_items[item_ix];

         /* Copy the data type and the pointer to the data into the item  */
         /* descriptor. To allow as much of a supplied string as possible */
         /* to be output, set the x co-ordinate to the length of the      */
         /* description field, plus a few spaces.                         */

         item->data_type = data_type;
         item->data_ptr  = data_ptr;

         if ( data_type == D_STRING )
            x = strlen(item->description) + ITEM_DESC_X + 5; /* 5 spaces  */
         else
            x = ITEM_DATA_X;

         /* Output the data */

         put_item_data ( x, item->y_coord, data_type, data_ptr );

         break;
      }
   }

/**/

   /* Restore the system colors                                           */

   wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );

   if ( item == NULL ) {

      /* Did not find the selected menu item, so output an error msg      */

      put_status_string ( "put_menu_item_data: non-existant select key" );
   }

} /* put_menu_item_data */

/**/

char readchar ( void )

/***************************************************************************
 This function reads and returns a character from the keyboard. As might
 be expected, some special processing is performed here. In particular, 
 this routine handles incoming control keys from the keyboard (those that 
 start with NUL (0x00)), and also implements some local actions, such as
 clear screen, and immediate killing of the program.

 For control keys, the second byte is returned with the upper bit set to 
 indicated that it was a control key. The MACMENU.DEF file defines several
 commonly used control keys using this approach.

 For clear screen, the char "`" causes a clear screen. The char ^C causes
 immediate termination of the program.

 NOTE THAT THIS FUNCTION IS AT THIS LOCATION, RATHER THAN LOCATED WITH THE
 REST OF THE PRIVATE FUNCTIONS DUE TO ITS NEED TO USE PUT_MENU. SINCE C
 DOES NOT SUPPORT A "FORWARD" DECLARTION, IT MUST BE LOCATED AFTER 
 PUT_MENU, AND BEFORE GET_MENU_SELECTIONS.
 ***************************************************************************/

{
   int  key              = ' ';
   BOOL done             = FALSE;
   BOOL command_modifier = FALSE;

   wrefresh ( lm_win );               /* Force a screen update             */

   while ( !done ) {

      key = getch ();

      switch ( key ) {

         case ncurses_F1:             /* F1 key pressed                    */
           key  = CMD_PF1;
           done = TRUE;
           break;

         case ncurses_F3:             /* F3 key pressed                    */
           key  = CMD_PF3;
           done = TRUE;
           break;

         case 0x03:                   /* Control C - Exit                  */
           exit_macmenu ();
           exit ( 1 );
           break;

         case '`':                    /* Redraw the screen                 */
            put_menu ( curr_menu );
            wmove ( lm_win, COMMAND_Y, COMMAND_X + cp_len );
            break;

         default:                     /* No special conditions, return key */
            done = TRUE;
            break;
      }

   } /* while not done */

   return ( (char) key );

} /* readchar */

/**/

char get_menu_selection ( selected_menu )

/*****************************************************************************
 This function reads a single command key and returns it to the caller.
 Only keys which are valid for the indicated menu are allowed: if the
 user enters some other key, he/she is prompted to enter another.

 This function returns the validated command key.
 *****************************************************************************/

   MENU_ID selected_menu;      /* The menu for which a command is desired */

{
   struct t_menu *menu;       /* Pointer to the selected menu             */
   struct t_menu_item *item;  /* An item within the selected menu         */
   char   menu_selection;     /* Entered command                          */
   int    cmd_ix;             /* Index of entered command                 */
   int    i, j;               /* Loop counters                            */

   menu   = &menus[selected_menu];
   cmd_ix = -1;

   while ( cmd_ix == -1 ) {

      clr_sys_line ( LT_COMMAND );
      wmove ( lm_win, COMMAND_Y, COMMAND_X + cp_len );

      menu_selection = readchar();
      if ( !menu->case_sensitive )
         menu_selection = toupper(menu_selection);

      /* Special escape -> Use the "~" character as an escape indicator. */
      /* Simply read and return the next character, with no checking.    */
      /* This allows the use of "hidden" commands! :-)                   */

      if ( menu_selection == '~' )
         return ( readchar() );

      for ( i = 0; i < MAX_ITEMS; i++ ) {

         /* Scan all the existing command keys for this menu, and see if   */
         /* this is a valid command key. Since command keys can be         */
         /* aliased, and the real select key is always loaded as the first */
         /* alias, scan just the alias list to see if we have a match.     */

         item = &menu->menu_items[i];
         
         for ( j = 0; j < sizeof (item->select_alias); j++ )

            if ( item->select_alias[j] == menu_selection ) {

               /* It is a valid selection. Set up the index and leave     */

               cmd_ix         = i;
               menu_selection = item->select_key;
               break;
            }
      }

      if ( cmd_ix == -1 ) {

         /* The user's selection was not valid. Issue an error message    */
         /* and try again.                                                */

         put_status_string ( "Invalid menu selection - Again, please?" );
         put_beep ();
      }

   } /* while no valid command */

   return ( menu_selection );

} /* get_menu_selection */

/**/

char get_command_key ()

/****************************************************************************
 This function moves the cursor to the command line and reads a single key.
 The uppercase version of key read is returned as the value of this function.
 ****************************************************************************/

{
      clr_sys_line ( LT_COMMAND );
      wmove ( lm_win, COMMAND_Y, COMMAND_X + cp_len );

      return ( toupper(readchar()));

} /* get_command_key */

/**/

int get_command_string ( menu_string )

/*****************************************************************************
 This function reads a string from the command line of the menu, and
 returns it in the supplied string. The length of the string is returned
 as the value of this function.

 This function returns the read string, and its length. If the user enters
 a blank carriage return, the returned length is 0.
 *****************************************************************************/

 char    *menu_string;         /* Pointer to the string buffer to use        */

{
   register int len;           /* Length of user's input string              */
   struct t_menu *menu;        /* Pointer to the current menu                */

   menu = &menus[curr_menu];

   clr_sys_line ( LT_COMMAND );

   mvwgetstr ( lm_win, COMMAND_Y, COMMAND_X + cp_len, menu_string );
   len = strlen(menu_string);

   if ( len > screenwidth - (COMMAND_X + cp_len) - 2 ) {

      /* The user's input overflowed the command line and wrapped around onto */
      /* status/response line. Redraw that line.                              */

      set_line_color ( menu->sys_color_pair, STATUS_Y, menu->sys_fg, menu->sys_bg );
      wattron ( lm_win, COLOR_PAIR(menu->sys_color_pair) );
      mvwaddstr ( lm_win, STATUS_Y, STATUS_X, status_prompt );
   }

   return ( len );

} /* get_command_string */

/**/

void clr_status_line ( void )

/*****************************************************************************
 This function clears the status line. It returns no value. 
 *****************************************************************************/

{
   clr_sys_line ( LT_STATUS );
}


void clr_command_line ( void )

/*****************************************************************************
 This function clears the command line. It returns no value. 
 *****************************************************************************/

{
   clr_sys_line ( LT_COMMAND );
}

