#****************************************************************************
# Cmenu.pm -- Perl Curses Menu Support Facility 
#
# Last updated Time-stamp: <01/04/07 18:11:21 devel> 
# 
#
# Author:  Andy Ferguson (andy@moil.demon.co.uk)
#          Holywood Rudolf Steiner School
#          Holywood, Northern Ireland
#          BT18 0PR
#
# derived from [perlmenu] (Version +4.0):
#          Steven L. Kunz
#          Networked Applications
#          Iowa State University Computation Center
#          Ames, IA  50011
#
# Date:    Version 5.0 -- Jan, 2001 -- Complete re-write using Curses code and dialog
#				       'look and feel'. Simplified sub-calls and 
#                                      colour support. Preferences stored in file.
#          Version 5.1 -- Jan, 2001 -- fencepost errors and field edit fixes
#          Version 5.2 -- Jan, 2001 -- bugfix and check for Curses >1.03
#          Version 5.3 -- Feb, 2001 -- Use hash for kseq and fix numeric data entry
#          Version 1.0 -- Apr, 2001 -- Minor fixes and revised for CPAN
#
# Notes:   Perl4 - Will not work since it relies on Curses.pm
#                  Use perlmenu.pm instead
#          Perl5 - Requires "Curses" extension available from any CPAN
#		   site (http://www.perl.com/CPAN/CPAN.html).
#                  Will also require Text::Wrap for splash screen calls
#                  
#                  Put the following at top of your code:
#
#                    use Curses;
#                    use Cmenu;
#
# Use:
#             &menu_initialise("title","advice");
#             &menu_init(1,"title");
#             &menu_item("Topic 1","got_1"....);
#             &menu_item("Topic 2","got_2"....);
#             ...
#             &menu_item("Topic n","got_n"....);
#             $sel_text = &menu_display("Select using arrow keys");
#             ...
#             &menu_terminate();
#
#
# Cmenu - Perl library module for curses-based menus & data-entry 
# Copyright (C) 2001     Andy Ferguson, AFC Commercial, Bangor BT19 1PF, UK
#
#    This Perl library module is free software; you can redistribute it
#    and/or modify it under the terms of the GNU Library General Public
#    License (as published by the Free Software Foundation) or the
#    Artistic License.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Library General Public License for more details.
#
#    You should have received a copy of the GNU Library General Public
#    License along with this library; if not, write to the Free
#    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#****************************************************************************
#
#****************************************************************************
# BUGS
#
# 1.   Refresh does not redraw top and bottom few lines of the backdrop
#      if the display gets totally trashed
#      Tried defining "screen" as a "window" to no avail
#
# 2.   Cannot find the ncurses BACK-TAB key so there is no default BACK
#      function. User needs to map this to another key
#
# 3.   Does not resize when called as a subshell or whatever, eg. as when called
#      from within "mc". Rsize works OK in a basic xterm.
#
# 4.   No line checking with multi-line subtitles; subtitle or pane may
#      overflow window
#****************************************************************************

package Cmenu;

use Curses;
use Text::Wrap;
use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;

@ISA = qw(Exporter);
# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
@EXPORT = qw(
	     menu_initialise 
	     menu_init
	     menu_item
	     menu_display
	     menu_show
	     menu_popup
	     menu_button_set
	     menu_terminate
	    );

@EXPORT_OK = qw (
		 $menu_sep
		 $menu_sepn
	       );

$Cmenu::VERSION ='1.00';

use vars qw($VERSION $menu_sep $menu_sepn);

BEGIN {
  # Field seperator characters returned after menu activity
  # menu_sep seperates individual fields
  # menu_sepn breaks up field name from field contents
  $menu_sep="¬";
  $menu_sepn="~";
}
  
# ##################################################################################
# Public variables and functions
# ==============================
# These variables and functions are available for user programs
# While others may be accessed, they are in fact surreal
# and may cease to exist in later releases
# ---< variables >------------------------------------------------------------------
# $menu_screen          : the base curses screen
# $menu_screen_lines    : current logical depth of the screen
# $menu_screen_cols     : current logical width of the screen
# $menu_inlay           : the main display
# $menu_inlay_lines     : depth of the menu inlay
# $menu_inlay_cols      : width of the menu inlay
# $menu_inlay_y         : y offset of inlay from screen top 0
# $menu_inlay_x         : x offset of inlay from screen left 0
# $menu_advice          : standard text for display at foot
# ---< functions >------------------------------------------------------------------
# All menu functions generally apply keypad, echo and other Curses ops, on
# return from any function Curses settings can be guaranteed as if these calls 
# had been made directly
#    &echo();
#    &nocbreak();
#    &curs_set(1);
# keypad control is only applied to new windows which should always be destroyed
# before returning.
# ----------------------------------------------------------------------------------
# &menu_initialise      : sets up all menu variables and constructs
# &menu_button_set      : swicthes menu buttons on and off
# &menu_item            : create a menu item
# &menu_display         : display a menu and get a response from it
# &menu_popup           : flash a busy window
# &menu_show            : gives a full screen text display
# &menu_terminate       : close the menu environment down
# ----------------------------------------------------------------------------------
# All Curses functions can of course be used in user programs but be aware that
# management of all windows then becomes the users responsibility and behaviour
# of the menuing environment may be unpredictable
# ##################################################################################

# ##################################################################################
# Variable Definitions
# ##################################################################################

my $did_initterm = 0;	# We already got escape sequences for arrows, etc.

# Keystroke arrays
my %kseq=();               # hash for function key translation
my $key_max=0;             # longest keystroke

# Windows
my $menu_screen;           # the backdrop
my $menu_inlay;            # background for the menu window with shadow etc
my $menu_window;           # menu window with text elements
my $menu_pane;             # where the menu options actually get drawn
my $menu_popup;            # special for popup and splash displays

# Window elements
my $menu_title;            # title of script in backdrop
my $menu_top_title;        # title of menu
my $menu_sub_title;        # sub-title of a menu
my $menu_sub_title_lines;  # depth of sub-title
my $menu_advice;           # message at foot of backdrop
my $menu_item_pos;         # where menu items will start 
my $menu_indent;           # where menu item labels will start

my $menu_index;            # counter of menu items

# Extent of display screen - fixed - unchangeable - from TERM settings
# Always starts at 0,0
my $menu_screen_cols=0;         # - COLS from Curses   } size of the full screen
my $menu_screen_lines=0;        # - LINES from Curses  }

# Extent of Menu Inlay - size and position of main window
# Amendable via preferences
# Mono screens lose the shadow so get a bigger inlay
my $menu_inlay_lines=0;
my $menu_inlay_cols=0;
my $menu_inlay_y=3;         # 2 for mono
my $menu_inlay_x=6;         # 4 for mono

# Extent of Menu text pane
# All defined at runtime depending on the menu items
my $menu_pane_lines=0;
my $menu_pane_cols=0;
my $menu_pane_y=0;
my $menu_pane_x=0;
my $menu_pane_scroll; 

my $menu_resized=0;          # trigger for terminal resizing
my $menu_style=0;            # 
my $max_item_len=0;          # longest menu item
my $max_sel_len=0;           # longest label length
my $menu_top_option=0;       # current menu item at the top of the display
my $menu_cur_option=0;       # the active menu option during navigation
   
# define global colour variables
my @menu_colour=();          # container for colours
# these get filled in after Curses has been initialised
my $menu_hascolor;
my $menu_att_backdrop;
my $menu_att_advice  ;
my $menu_att_text    ;
my $menu_att_title   ;
my $menu_att_option  ;
my $menu_att_button  ;
my $menu_att_scroll  ;
my $menu_att_rtext   ;
my $menu_att_rtitle  ;
my $menu_att_roption ;
my $menu_att_edge    ;
my $menu_att_dull    ;
my $menu_att_help    ;
my $menu_att_warn    ;
my $menu_att_error   ;
my $menu_att_popup   ;
 

# Initialise menu item arrays
my @menu_sel_text =();             # Menu item text
my @menu_sel_style =();            # Menu item type
my @menu_sel_label = ();           # Menu item label
my @menu_sel_flag = ();            # Menu item special
my @menu_sel_return = ();          # value to be returned on selection
my @menu_sel_pos = ();             # Menu item position (data fields)
                                   #   max length + dec.places + 0

# Set the default file for help display
my $menu_help="help.txt";
my $menu_help_root="/etc/Cmenu/";

# Initialise button arrays
my @menu_button = ();
my @menu_button_action = ();
# Default text for buttons
$menu_button[1]="Select";
$menu_button[2]="Help";
$menu_button[3]="Exit";
# What each button will do if pressed
$menu_button_action[1]="ACCEPT";
$menu_button_action[2]="HELP";
$menu_button_action[3]="QUIT";

my $menu_buttons=3;                # start with all buttons available
my $menu_hot_button=1;             # first button will be active

my $buffer = "";                   # temp storage for field editing

# ##################################################################################
# Initialisation Routines
# ##################################################################################

#**********
#  MENU_INITIALISE
#
#  Function:	Setups Curses elements and prepares a backdrop
#		Also define terminal atributes and defines default colours
#		(these can be changed after this function has been called)
#
#  Call format:	&menu_initialise("title string","advice note string");
#
#  Arguments:   - the title of the Menu application
#                 this is displayed in the top left-hand corner of all screens 
#               - an advice note to be displayed on all pages 
#                 normally displayed at the foot of each screen
#                 may be replaced by user comments with &menu_advice routine
#
#  Returns:	Main window - this can be referenced externally for 
#               direct drawing by user program (untested)
#**********
sub menu_initialise {
 my ($title,$advice)=@_;
 my ($key,$action);
 my @menu_colour = ();

 $menu_title=$title;
 $menu_advice=$advice;

 # Only do this once
 if($did_initterm==0) {

# ##################################################################################
# BLOCK 1
# =======
# Initialise curses structures
# ##################################################################################

   if(!$menu_screen) {          # Checks whether user has done this already
     $menu_screen=&initscr();   # create a curses structure
   }                       # auto save tty settings to be restore at end by endwin

   clear();                # clear the screen
   
# ##################################################################################
# BLOCK 2
# =======
# Setup key sequences for input filtering
# These are the defaults - may be over-ridden by loaded preferences
# ##################################################################################

# ##################################################################################
# Unable to test the alternative getcap/tigetstr/tput settings so these are
# left alone. tigetstr continues to not work on Linux/Curses
# --------------------------------------------------------------------------------
# Functional mappings are
# Cursor movement
#   UP       : move up
#   DOWN     : move down
#   RITE     : move right - can mimic lynx-style motion
#   LEFT     : move left - can mimic lynx-style motion
# Large cursor movement
#   HOME     : go to top (of menu)
#   END      : go to bottom (of menu)
#   NEXT     : next page 
#   PREV     : previous page
#   JUMP     : leap to a specific menu item 
# action
#   HELP     : display active help page/info
#   RET      : action current selection
#   EXIT     : cancel or abort current operation
#   QUIT     : go back a menu
#   SPACE    : toggle radio button or action button (equiv to RET)
#   REFS     : refresh the screen
# button or field navigation
#   TAB      : next field
#   BACK     : previous field
# text buffer editing
#   DEL      : delete chracter right
#   BS       : delete character left
#   INS      : toggle insert mode
#   KILL     : kill current line to buffer
#   YANK     : yank buffer
#   BUFF     : empty the text buffer
# Specials
# KEY_RESIZE : } trap screen resizing   } these are input from Curses
#   401      : }   "     "       "      } not by the user
#   NOP      : do nothing
# --------------------------------------------------------------------------------
 
# ==================================================================================
# Next blocks are system specific - choose the one(s) you need
# ==================================================================================
# Method 1 (getcap) - UNTESTED
# Uncomment if you have "getcap"
# --------------------------------------------------------------------------------
#   $kseq{&getcap('ku')}="UP";	  # Cursor-up
#   $kseq{&getcap('kd')}="DOWN";  # Cursor-down
#   $kseq{&getcap('kr')}="RITE";  # Cursor-right
#   $kseq{&getcap('kl')}="LEFT";  # Cursor-left
#   $kseq{&getcap('cr')}="RET";	  # Carriage-return
#   $kseq{&getcap('nl')}="RET";	  # New-line

# --------------------------------------------------------------------------------
# Method 2 (tigetstr) - UNTESTED
# Uncomment if you have tigetstr (Solaris) instead of "getcap"
# --------------------------------------------------------------------------------
#   $kseq{&tigetstr('kcuu1')}="UP";	  # Cursor-up
#   $kseq{&tigetstr('dcud1')}="DOWN";	  # Cursor-down
#   $kseq{&tigetstr('kcuf1')}="RITE";	  # Cursor-right
#   $kseq{&tigetstr('kcub1')}="LEFT";	  # Cursor-left
#   $kseq{&tigetstr('cr')}="RET";	  # Carriage-return
#   $kseq{&tigetstr('nl')}="RET";	  # New-line

# --------------------------------------------------------------------------------
# Method 3 (tput)
# Uncomment if you have terminfo (and tput) instead of "getcap"
# Works for modern Linux
# --------------------------------------------------------------------------------
   $kseq{`tput kcuu1`}="UP";	  # Cursor-up
   $kseq{`tput kcud1`}="DOWN";	  # Cursor-down
   $kseq{`tput kcuf1`}="RITE";	  # Cursor-right
   $kseq{`tput kcub1`}="LEFT";	  # Cursor-left
   $kseq{`tput kent`}="RET";	  # Carriage-return
		# HP-UX 9.05 users: try $kseq[4] = `tput cr` if
		#                   "tput kent" gives errors
   $kseq{ `tput nel`}="RET";	  # New-line
# --------------------------------------------------------------------------------
# Method 4
# Explicit control sequences - should work for all terminals regardless
# These should be Uncommented for all systems/platforms
# Hacks for Xterms and standard emacs style definitions
# --< Xterm hacks >---------------------------------------------------------------
   $kseq{"\033[A"}="UP";        # Ansi cursor-up (for DEC xterm)
   $kseq{"\033[B"}="DOWN";      # Ansi cursor-down (for DEC xterm)
   $kseq{"\033[C"}="RITE";      # Ansi cursor-right (for DEC xterm)
   $kseq{"\033[D"}="LEFT";      # Ansi cursor-left (for DEC xterm)
   # added mapping for Home/End and Page buttons for xterms
   $kseq{"\033[E"}="PREV";    # Ansi guess (for DEC xterm)
   $kseq{"\033[F"}="END";     # Ansi end key (for DEC xterm)
   $kseq{"\033[G"}="NEXT";    # Ansi guess (for DEC xterm)
   $kseq{"\033[H"}="HOME";    # Ansi home key (for DEC xterm)
# --< kvt specials >---------------------------------------------------------------
# --  KDE2 and its terminal kvt do funny things with the keys
# --  keymaps may be lost entirely with the key bindings
   $kseq{scalar(KEY_SELECT)}="END";   # end key (for kvt)
   $kseq{scalar(KEY_FIND)}="HOME";    # home key (for kvt)
# --< Emacs hacks >---------------------------------------------------------------
   $kseq{"\cA"}="HOME";       # begin of line
   $kseq{"\cE"}="END";        # end of line
   $kseq{"\cF"}="RITE";       # next char
   $kseq{"\cB"}="LEFT";       # prev char
   $kseq{"\cN"}="TAB";        # next field
   $kseq{"\cP"}="BACK";       # prev field
   $kseq{"\cL"}="REFS";       # redraw screen
   $kseq{"\cD"}="DEL";        # delete right
   $kseq{"\cK"}="KILL";       # kill line
   # Normally yank_line would be "\cY" (C-y), unfortunately both C-z and C-y are
   # are used to send the suspend signal in our environment. Bind it to
   # C-v for a lack of anything better.
   $kseq{"\cV"}="YANK";       # yank/paste buffer
   # buffer
   $kseq{"\cX"}="BUFF";       # copy and kill to buffer

# --------------------------------------------------------------------------------
# Method 5
# Standard PC keyboard commands
# with ncurses keypad turned on - these should work for any terminal type
# either termcap or terminfo
# --------------------------------------------------------------------------------
   $kseq{scalar(KEY_HOME)}="HOME";    # home
   $kseq{scalar(KEY_END)}="END";      # end
   $kseq{scalar(KEY_PPAGE)}="PREV";   # page up
   $kseq{scalar(KEY_NPAGE)}="NEXT";   # page down
   $kseq{scalar(KEY_IC)}="INS";       # insert toggle
   $kseq{scalar(KEY_DC)}="DEL";       # delete
   $kseq{scalar(KEY_BACKSPACE)}="BS"; # backspace
   $kseq{"\cI"}="TAB";                # tab
   $kseq{scalar(KEY_BTAB)}="BTAB";    # shifted tab
   $kseq{scalar(KEY_UP)}="UP";        # up arrow
   $kseq{scalar(KEY_DOWN)}="DOWN";    # down arrow
   $kseq{scalar(KEY_LEFT)}="LEFT";    # left arrow
   $kseq{scalar(KEY_RIGHT)}="RITE";   # right arrow
   $kseq{scalar(KEY_ENTER)}="RET";    # enter key
   $kseq{scalar(KEY_BREAK)}="EXIT";   # break
   $kseq{"\cJ"}="RET";                # normal return key

   # Functions keys have no special meaning - user mapable
   # some helpful defaults are set here but are not necessary
   $kseq{scalar(KEY_F(1))}="HELP";    # Func key 1
   $kseq{scalar(KEY_F(2))}="NOP";     # Func key 2
   $kseq{scalar(KEY_F(3))}="NOP";     # Func key 3
   $kseq{scalar(KEY_F(4))}="NOP";     # Func key 4
   $kseq{scalar(KEY_F(5))}="NOP";     # Func key 5
   $kseq{scalar(KEY_F(6))}="NOP";     # Func key 6
   $kseq{scalar(KEY_F(7))}="NOP";     # Func key 7
   $kseq{scalar(KEY_F(8))}="QUIT";    # Func key 8
   $kseq{scalar(KEY_F(9))}="EXIT";    # Func key 9
   $kseq{scalar(KEY_F(10))}="NOP";    # Func key 10
   $kseq{scalar(KEY_F(11))}="NOP";    # Func key 11
   $kseq{scalar(KEY_F(12))}="EXIT";   # Func key 12

# ##################################################################################
# BLOCK 3
# =======
# Load defaults from a config file if it exists
# ##################################################################################

   # Set defaults

   # Default colour preferences
   $menu_colour[0]=COLOR_BLUE;  
   $menu_colour[1]=COLOR_CYAN;
   $menu_colour[2]=COLOR_WHITE;
   $menu_colour[3]=COLOR_BLUE;
   $menu_colour[4]=COLOR_BLACK;
   $menu_colour[5]=COLOR_YELLOW;
   $menu_colour[6]=COLOR_RED;
   $menu_colour[7]=COLOR_WHITE;
   $menu_colour[8]=COLOR_GREEN;

   if(-e "/etc/Cmenu/.cmenurc") {
     # load system wide preferences
     &menu_config_file("/etc/Cmenu/.cmenurc");
   }
   if(-e "~/.cmenurc") {
     # Load the users specific preferences
     &menu_config_file("~/.cmenurc");
   }
   if(-e "cmenurc") {
     # Load the application specific preferences
     &menu_config_file("cmenurc");
   }
   
# ##################################################################################
# BLOCK 4
# =======
# Sort out colour stuff and establish styles
# ##################################################################################

   # check and do colour stuff
   $menu_hascolor = eval { has_colors() };
   
   if ($menu_hascolor) {	
     start_color();
     
     init_pair(1, $menu_colour[1] , $menu_colour[0] ); # backdrop
     init_pair(2, $menu_colour[4] , $menu_colour[2] ); # window text
     init_pair(3, $menu_colour[5] , $menu_colour[2] ); # window title - bold
     init_pair(4, $menu_colour[6] , $menu_colour[2] ); # window option - bold
     init_pair(5, $menu_colour[7] , $menu_colour[2] ); # window button - bold
     init_pair(6, $menu_colour[8] , $menu_colour[2] ); # scroller - bold
     init_pair(7, $menu_colour[2] , $menu_colour[3] ); # reverse text - BOLD
     init_pair(8, $menu_colour[1] , $menu_colour[3] ); # reverse title - bold
     init_pair(9, $menu_colour[5] , $menu_colour[3] ); # reverse option - bold
     
     init_pair(10,$menu_colour[2] , $menu_colour[2] ); # edge bright - bold
     init_pair(11,$menu_colour[4] , $menu_colour[2] ); # edge dull 
     
     init_pair(12,COLOR_BLACK     , COLOR_BLACK     ); # shadow
     init_pair(13,$menu_colour[5] , $menu_colour[2] ); # help
     init_pair(14,$menu_colour[6] , $menu_colour[8] ); # warn
     init_pair(15,$menu_colour[7] , $menu_colour[6] ); # error
     init_pair(16,$menu_colour[5] , $menu_colour[8] ); # popup
     
     $menu_att_backdrop = COLOR_PAIR(1)|A_BOLD;
     $menu_att_advice   = COLOR_PAIR(1);
     $menu_att_text     = COLOR_PAIR(2);
     $menu_att_title    = COLOR_PAIR(3)|A_BOLD;
     $menu_att_option   = COLOR_PAIR(4);
     $menu_att_button   = COLOR_PAIR(5)|A_BOLD;
     $menu_att_scroll   = COLOR_PAIR(6)|A_BOLD;
     $menu_att_rtext    = COLOR_PAIR(7)|A_BOLD;
     $menu_att_rtitle   = COLOR_PAIR(8)|A_BOLD;
     $menu_att_roption  = COLOR_PAIR(9)|A_BOLD;
     $menu_att_edge     = COLOR_PAIR(10)|A_BOLD;
     $menu_att_dull     = COLOR_PAIR(11)|A_BOLD;
     $menu_att_help     = COLOR_PAIR(13);
     $menu_att_warn     = COLOR_PAIR(14);
     $menu_att_error    = COLOR_PAIR(15);
     $menu_att_popup    = COLOR_PAIR(16)|A_BOLD;
     
   } else {
     # for monochrome terminals
     $menu_att_backdrop = A_NORMAL;
     $menu_att_advice   = A_NORMAL;
     $menu_att_text     = A_NORMAL;
     $menu_att_title    = A_BOLD;
     $menu_att_option   = A_BOLD;
     $menu_att_button   = A_BOLD;
     $menu_att_scroll   = A_NORMAL;
     $menu_att_rtext    = A_REVERSE|A_NORMAL;
     $menu_att_rtitle   = A_REVERSE|A_BOLD;
     $menu_att_roption  = A_REVERSE|A_BOLD;
     $menu_att_edge     = A_BOLD;
     $menu_att_dull     = A_NORMAL;
     $menu_att_help     = A_REVERSE;
     $menu_att_warn     = A_REVERSE|A_BOLD;
     $menu_att_error    = A_REVERSE|A_BOLD;
     # Make inlay larger since there is no shadow
     $menu_inlay_y=$menu_inlay_y-1;
     $menu_inlay_x=$menu_inlay_x-2;
   }

# ##################################################################################
# BLOCK 5
# =======
# Draw the screens backdrop and establish dimensions
# ##################################################################################

   &menu_redraw_backdrop();

   $did_initterm = 1;
 }

 # Calculate the longest keystroke sequence
 $key_max=0;
 foreach $key(sort(keys(%kseq))) {
   if(length($key)>$key_max) { $key_max=length($key); }
    }
}

#**********
#  MENU_CONFIG_FILE
#
#  Function:	Loads user over-rides from a config file
#
#  Call format:	&menu_config_file("filename");
#
#  Notes:       See the modules sample file for data structure
#**********
sub menu_config_file {
  my ($filename) = @_;
  my ($type,$key,$action);

  open(IN,"<$filename");
  while(<IN>) {
    if(length($_)>1) {
      chop;
      ($type,undef,$key,$action)=split(/:/);
      if($type eq "C") { $menu_colour[$key]=$action; }
      if($type eq "H") { $menu_help_root=$action; }
      if($type eq "K") { $kseq{$key}=$action; }
    }
  }
  close(IN);
}


#**********
#  MENU_REDRAW_BACKDROP
#
#  Function:	Draws the backdrop and then inlay
#
#  Call format:	&menu_redraw_backdrop();
#
#  Notes:       Also called to refresh the screen on command or after resizing
#**********
sub menu_redraw_backdrop {
  # Setup Curses dimensions
  $menu_screen_cols=COLS;
  $menu_screen_lines=LINES;
  $menu_resized=0;		# flag to detect subsequent resizing
                                # this only works on a direct xterm - spawned xterms such
                                # as mc sub-shells will not be detected
  
  # Draw title on the backdrop
  # Backdrop fills whole screen with a title at the top (left-just)
  # and a small advice note at the foot (centred)
  &bkgd($menu_screen,$menu_att_backdrop);
  &clear($menu_screen);
  if(length($menu_title)<1) {
    &addstr($menu_screen,0,1,"Cmenu Menu");
  } else {
    &addstr($menu_screen,0,1,$menu_title);
  }
  &move($menu_screen,1,1);
  &hline($menu_screen,ACS_HLINE,$menu_screen_cols-2);
  # Display system advice message
  &attrset($menu_screen,$menu_att_advice);
  &move($menu_screen,$menu_screen_lines-1,0);
  if(length($menu_advice)>0) {
    &addstr($menu_screen,$menu_screen_lines-1,($menu_screen_cols-length($menu_advice))/2,$menu_advice);
  }
  
  if($menu_hascolor) {
    # Draw basic Window inlay with shadow (colour only)
    attrset($menu_screen,COLOR_PAIR(12));
    move($menu_screen,$menu_inlay_y+1,$menu_screen_cols-$menu_inlay_x);
    vline($menu_screen," ",$menu_screen_lines-($menu_inlay_y*2));
    move($menu_screen,$menu_inlay_y+1,$menu_screen_cols-$menu_inlay_x+1);
    vline($menu_screen," ",$menu_screen_lines-($menu_inlay_y*2));
    move($menu_screen,$menu_screen_lines-$menu_inlay_y,$menu_inlay_x+2);
    hline($menu_screen," ",$menu_screen_cols-($menu_inlay_x*2)-2);
  }
  # Create Window insert
  $menu_inlay=newwin($menu_screen_lines-($menu_inlay_y*2),$menu_screen_cols-($menu_inlay_x*2),$menu_inlay_y,$menu_inlay_x);
  bkgd($menu_inlay,$menu_att_text);
  &clear($menu_inlay);
  
  noutrefresh($menu_screen);
  
  # Sets bounds for Window inlay
  $menu_inlay_lines=$menu_screen_lines-($menu_inlay_y*2);
  $menu_inlay_cols=$menu_screen_cols-($menu_inlay_x*2);
}

#**********
#  MENU_TERMINATE
#
#  Function:	Closedown all Curses structures and quit
#
#  Call format:	&menu_terminate("Message text");
#
#  Arguments:	- Text message to be left on the screen when program finishes
#                 This is not a Curses string, just a simple Perl echo
#
#  Returns:	Peace and Happiness for all ManKind
#**********
sub menu_terminate {
  my ($message)=@_;

  &delwin($menu_inlay);
  &standend();
  &clear();
  &refresh();                 # clears the screen
  &curs_set(1);               # turn the cursor back on
  &endwin();                  # closes all structures and auto restores tty
  print "$message\r\n";
  exit();
}

# ##################################################################################
# Menu Processing and Navigation
# ##################################################################################

#**********
#  MENU_INIT
#
#  Function:	Initialize a new menu structure: menu arrays, title, and "top" flags.
#
#  Call format:	&menu_init("Top Title","Sub Titles","HelpFile");
#
#  Arguments:   - "Top Title" is the title of the menu displayed centred in
#                 the window inlay
#               - "Sub Title" is text comments provided to describe the menu or
#                 give clues to its usage; user provided.
#                 Normally centred unless greater than the width of the window
#               - "HelpFile" defines a help file to be displayed when the
#                 help key is pressed. Help files can be associated with individual
#                 menu items so this file is used when an item has no help file
#                 See menu_help for more information on these help facilities
#
#  Returns:	Window value from "initscr" call.
#
#**********
sub menu_init {
  my ($top_title,$sub_title,$help_page) = @_;

  my ($i,$justify);


  $menu_top_title=$top_title;
  $menu_sub_title=$sub_title;

  # Sort out undefined variables to their default
  if(!$help_page) {
    $menu_help="help.txt"; 
  } else {
    $menu_help=$help_page;
  }

  &menu_draw_inlay();

#  $item_lines_per_screen = $last_line - $first_line + 1;

# Init menu items array
  @menu_sel_text = ();		# Selection text for each item
  @menu_sel_label = ();	        # Action text/label for each item
  @menu_sel_style = ();         # Display style for menu item
  @menu_sel_flag = ();  	# Data associated with menu item
  @menu_sel_pos = ();           # Position for data field
  $menu_index = 0;		# menu item counter

# Init some other key variables
  $max_item_len = 0;		# max length of menu item text
  $max_sel_len = 0;             # length of selection text/label

# Return window value from "initscr" call.
  &noutrefresh($menu_inlay);
  $menu_inlay;
}

#***********
#  MENU_ITEM
#
#  Function:	Add an item to the active menu.
#               Maintained as a set of arrays
#               This function now controls style of presentation
#               so that a menu can have mixed type options
#
#  Call format:	&menu_item("Item text","Text label",item_style,pre_set_flag,"item_pos");
#
#  Arguments:	- "Item text" the string presented in menu. Required.
#                 If this item is blank the menu item will be totally ignored
#                 This may be useful when displaying blank fields or empty records
#		- "Text label" String returned if selected. Optional.
#                 This will also be used as an abbreviated name in some styles, 
#                 the first character being the option letter (auto-capitalised)
#                 Although some options do not need a label, it is strongly
#                 recomended that a label is provided
#               - Item style - how the menu item should be presented
#                 Notice that menu style is determined at item level rather
#                 than globally for the menu; this permits a mixture of menu
#                 items to be displayed
#                 Be careful though when mixing inappropriate types as the
#                 return values may be difficult to differentiate
#                 Possibilities include
#                    0 - (default) item with text labels
#                        when selected an item will return the Text_label
#                        which is also shown to the left of the item text
#                    1 - item with numeric labels
#                        selected items will still return the Text_label but
#                        the item text will be prefixed with an option number
#                        Option number are incremental; if you want to force
#                        a return number, use item type 8
#                    2 - radio buttons
#                        item text is prefixed with [ ] or [X] for unset and set
#                        items; setting of items is controlled by making
#                        pre_set_flag non-zero for an item
#                        the selected item returns its Text_label
#                    3 - check buttons
#                        as for radio buttons except styled as ( ) or (+)
#                        any number of buttons can be on
#                        returns a string of ON buttons (all other buttons
#                        may be assumed to be off). The string is a token
#                        spererated list, break out items with split function
#                        Field spererator is $menu_sep
#                        items are identified by their Text_label
#                    4 - left justified data field (display only)
#                        Text_labels are usually left-just, this type is for
#                        data forms; text_labels are right-just
#                        Data fields (item text) are left justified
#                        Text_label is returned 
#                    5 - right justified data field (display only)
#                        as for style 4 except item_text is right-just
#                        to the maximum width of the largest field
#                    6 - edit alphanumeric field
#                        displayed like a type 4 but if selected may be edited
#                        revised data is returned as a tokenised string
#                        Field sep is $menu_sepn, seperate using split
#                        Editing function assumes and permits alpha text
#                    7 - edit numeric field
#                        as for style 6 except only numeric data is permitted
#                        pre-set_flag can be used to specifiy the number of
#                        decimal places in the number
#                    8 - offset text label (return label stored in pre_set_flag
#                        Since the Text_label can be displayed as part of the menu
#                        it may not always be useful as a return value e.g. when
#                        displaying records. the pre_set_flag can be used instead
#                        as the return value
#                        The style of presentation is as for type 4
#                        used in data entry forms for style consistency when a regular
#                        menu option is required
#                    9 - seperator
#                        allows inactive spaces to be included in complex menus
#                        to break up the display
#                        The labels are used directly as textual seperators
#                        it must at least be " " or the item will be ignored
#		- pre_set_flag: Value to pre-set multiple selection menu. Optional.
#		  (neg=lockout selection,0=allow selection,1=preset selection)
#                 For radio/checklists - toggles options on (1) or off (0)
#                 For offset items this is the value to be returned
#               - item_pos: specifies the explicit location of a data field  
#                 expressed as a string of numbers
#                 For numeric field, indicates decimal precision
#                 For alpha-fields indicates maximum length
#                 eg 20 4 6
#                    20 max field length
#                     4 numbers before dec.pt
#                     6 numbers after dec.pt
#
#  Returns:	nothing useful or next menu index if you want it
#***********
sub menu_item {
  my ($item_text,$item_sel,$item_style,$item_data,$item_pos) = @_;
  my ($i);

# Sanity checks
  if (!$item_style) { $item_style = 0; }            # force null sets to zero
  if (!$item_text) {                                # return if there was no text
    if($item_style<6) {
      return($menu_index);
    } else {
      $item_text="";
    }
  }
  if (!$item_data) {                                 # force null sets to zero
    if(($item_style==6)||($item_style==7)) {         # set defaults for editing
      $item_data=$menu_index;
    } else {
      $item_data = 0; 
    }
  }
  if (!$item_pos) {                                 # force null sets to zero
    if(($item_style==6)||($item_style==7)) {         # set default for editing
      $item_pos="10 0 0";
    } else {
      $item_pos = 0; 
    }
  }

  # Adjust max length value (for centering menu) - do this later when displaying
  # Test the length of the item text
  if(($item_style==6)||($item_style==7)) {         # set default for editing
    ($i,undef,undef)=split(/ /,$item_pos);
    if ($i > $max_item_len) { $max_item_len = $i; }
  } else {
    $_ = length($item_text);
    if ($_ > $max_item_len) { $max_item_len = $_; }
  }
  # Test the length of the Text_label
  # For numeric or radio labels, this is forced to be 3 chracters long
  $_ = length($item_sel);
  if(($item_style==1)||($item_style==2)||($item_style==3)) {
    $_=3;
  }
  if (($_<3)) { $_=3; }
  if ($_ > $max_sel_len) { $max_sel_len = $_; }

# Load into arrays and adjust index
  $menu_sel_text[$menu_index] = $item_text;
  $menu_sel_flag[$menu_index] = 0;                # empty as defualt
  if (!$item_sel) {
    # if no selected text is given, use the number of the item index
    $item_sel= $menu_index;
  }
  $menu_sel_label[$menu_index] = $item_sel;        # usually the data label
  $menu_sel_return[$menu_index] = $item_sel;       # the data label is usually returned too
  $menu_sel_style[$menu_index] = $item_style;      # type of menu item
  # Deal with special item types needing extra data
  if(($item_style==2)||($item_style==3)) {
    $menu_sel_flag[$menu_index] = $item_data;      # toggle switches
  }
  if($item_style==6) {
    # editable field returns this label
    $menu_sel_return[$menu_index] = $item_data;    #
    # this is an editable field so set max length and position
    $menu_sel_pos[$menu_index] = $item_pos;        # 
  }
  if($item_style==7) {
    # editable field returns this label
    $menu_sel_return[$menu_index] = $item_data;    #
    # this is an editable field so set max length and position
    $menu_sel_pos[$menu_index] = $item_pos;        # 
  }
  if($item_style==8) {
    # offset return values
    $menu_sel_return[$menu_index] = $item_data;     # the data label is usually returned too
  }
  $menu_index++;
}

#**********
#  MENU_ADVICE
#
#  Function:	Displays an advice message at the foot of the screen
#
#  Call format:	&menu_advice([NULL|"advice string"]);
#
#  Arguments:	Advice message to be displayed
#               If no message is given, displays a standard default message
#               passed when module initialises (see menu_initialise)
#               This message is therefore self clearing
#
#  Returns:	user friendliness
#**********
sub menu_advice {
  my ($advice)=@_;

  if($menu_buttons==0) {
    # Display messages in button bar
    &attrset($menu_inlay,$menu_att_dull);
    &move($menu_inlay,$menu_inlay_lines-2,1);
    &clrtoeol($menu_inlay);
    move($menu_inlay,$menu_inlay_lines-2,$menu_inlay_cols-1);
    vline($menu_inlay,ACS_VLINE, 1);
    if(length($advice)>0) {
      &addstr($menu_inlay,$menu_inlay_lines-2,($menu_inlay_cols-length($advice))/2,$advice);
    } else {
      &addstr($menu_inlay,$menu_inlay_lines-2,($menu_inlay_cols-length($menu_advice))/2,$menu_advice);
    }
    &noutrefresh($menu_inlay);
  } else {
    # display messages at foot of screen
    &attrset($menu_screen,$menu_att_advice);
    &move($menu_screen,$menu_screen_lines-1,1);
    &clrtoeol($menu_screen);
    if(length($advice) > 0 ) {
      &addstr($menu_screen,$menu_screen_lines-1,($menu_screen_cols-length($advice))/2,$advice);
    } else {
      &addstr($menu_screen,$menu_screen_lines-1,($menu_screen_cols-length($menu_advice))/2,$menu_advice);
    }
    &noutrefresh($menu_screen);
  }
}

#**********
#  MENU_NAVIGATE 
#
#  Function:	Allows user to navigate the current menu until an items is selected
#		or the menu is exited
#
#  Call format:	&menu_navigate();
#
#  Returns:     selected menu item at exit point
#		%UP%    -- quit menu 
#               %EMPTY% -- abort menu
#
#  Notes:	1) This routine ALWAYS sets "nocbreak" and "echo" terminal 
#		   modes before returning.
#		2) This routine exits directly (after calling the optional 
#		   "quit" routine) if "q"|"Q" is pressed.
#               3) performs all menu functions including field editing
#                  returning any data as a tokenised string
#**********
sub menu_navigate {
 
  my ($help,$i,$j,$style,$new_option,$action,$trunc,$indent);
  my $ret="";
  
  # Check for no "menu_item" calls.
  if ($#menu_sel_text < 0) {
    return("%EMPTY%".$menu_sep);
  }
  
  # curses cookery
  cbreak();               # permits keystroke examination
  noecho();               # no input echo until enabled explicitly
  curs_set(0);            # turn the cursor off
  
  # reset and draw button bar
  $menu_hot_button=1;
  &menu_button_bar(0);
  
  &menu_draw_window();
  noutrefresh($menu_window);
  
  # Compute prepend length (for stuff we prepend to each selection text)
  #   indent   : indent to centre items
  #   item_pos : where item text starts
  $menu_item_pos=$max_sel_len+1;

  # Also trunc is number of characters to shorten text
  # decide whether or not to truncate menu items
  $trunc=($max_item_len+$menu_item_pos)-($menu_pane_cols-2);
  if($trunc<=0) {
    $menu_indent=($trunc*-1)/2;
    $trunc=0;
  } else {
    $menu_indent=0;
  }
  if($trunc>=$max_item_len) {
    # menu not wide enough to show anything so abort this menu
    &menu_advice("Item descriptions too long - Aborting!");
    &refresh();
    getch($menu_screen);
    $ret="%UP%".$menu_sep;
  } else {

    &menu_create_pane();

  GET_KEY:  do {
      # Draw up and down symbols
      # Skip the next next bit if everything fits on one page
      if(($menu_pane_lines-3)<$menu_index-1) {
	# Display page excess arrows
	move($menu_window,0,$menu_pane_scroll);
	if($menu_top_option>0) {
	  # there are items above this
	  attrset($menu_window,$menu_att_scroll);
	  addstr($menu_window, "-^-");
	} else {
	  attrset($menu_window,$menu_att_dull);
	  hline($menu_window,ACS_HLINE,3);
	}  
	move($menu_window,$menu_pane_lines-1,$menu_pane_scroll);
	if($menu_index-1>$menu_top_option+$menu_pane_lines-3) {
	  # there are items below this
	  attrset($menu_window,$menu_att_scroll);
	  addstr($menu_window, "-v-");
	} else {
	  attrset($menu_window,$menu_att_edge);
	  hline($menu_window,ACS_HLINE,3);
	}  
	noutrefresh($menu_window);
      }

      doupdate();

      # Collect key sequences until something we recoginize 
      # (or we know we don't care)
            $action = &menu_key_seq($menu_pane);

      # ------------------------------------------------------------------------------
      # Switch construct for dealing with key sequence input
      # ------------------------------------------------------------------------------
    KEYWAIT: for ($action) {
	# Set return value as current option
	$ret=$menu_sel_return[$menu_cur_option].$menu_sep;

	# General cursor movement
	/DOWN/ && do {		# down arrow
	  if($menu_cur_option==$menu_index-1) {
	    # Hit the bottom
	    &menu_advice("You are on the last entry!");
	  } else {
	    do {
	      menu_draw_line($menu_cur_option,$menu_indent);
	      $menu_cur_option++;
	      if(($menu_cur_option-$menu_top_option)>$menu_pane_lines-3) {
		&scrl($menu_pane,1);
		$menu_top_option++;
	      } 
	    } until ($menu_sel_style[$menu_cur_option]!=9);
	      &menu_draw_active($menu_cur_option,$menu_indent);
	    &noutrefresh($menu_pane);
	    &menu_advice("");
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/UP/ && do {		# Up arrow
	  if($menu_cur_option==0) {
	    # Hit the bottom
	    &menu_advice("You are on the first entry!");
	  } else {
	    do {
	      menu_draw_line($menu_cur_option,$menu_indent);
	      $menu_cur_option--;
	      if(($menu_cur_option-$menu_top_option)<0) {
		&scrl($menu_pane,-1);
		$menu_top_option--;
	      }
	      redo if ($menu_sel_style[$menu_cur_option]==9);
	      &menu_draw_active($menu_cur_option,$menu_indent);
	    };
	    &noutrefresh($menu_pane);
	    &menu_advice("");
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/LEFT/ && do {		# Left arrow
	  # Treat this like an UP-Menu request
	  $action="QUIT";
	  redo KEYWAIT;
	};
	/RITE/ && do {		# Right arrow
	  # Treat this like a RETURN
	  $action="RET";
	  redo KEYWAIT;
	};
	# larger cursor motion
	/PREV/ && do {		# Page up
	  if($menu_top_option<=0) {
	    # Hit the bottom
	    menu_advice("There are no more options!");
	  } else {
	    $menu_cur_option=$menu_cur_option-($menu_pane_lines-3);
	    $menu_top_option=$menu_top_option-($menu_pane_lines-3);
	    &menu_draw_pane();
	    &menu_advice("");
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/NEXT/ && do {		# Page down
	  if($menu_top_option>($menu_index-1-($menu_pane_lines-2))) {
	    # Hit the bottom
	    menu_advice("There are no more options!");
	  } else {
	    $menu_cur_option=$menu_cur_option+($menu_pane_lines-3);
	    $menu_top_option=$menu_top_option+($menu_pane_lines-3);
	    &menu_draw_pane();
	    &menu_advice("");
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/HOME/ && do {		# Home
	  if($menu_top_option==0) {
	    # Check if the top item is already on the screen
	    if($menu_cur_option==0) {
	      # Already at the top
	      &menu_advice("You are already at the top");
	    } else {
	      menu_draw_line($menu_cur_option,$menu_indent);
	      $menu_cur_option=0;
	      &menu_draw_active($menu_cur_option,$menu_indent);
	      &noutrefresh($menu_pane);
	      &menu_advice("");
	    }
	  } else {
	    $menu_top_option=0;
	    $menu_cur_option=0;
	    &menu_draw_pane();
	    &menu_advice("");
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/END/ && do {		# End
	  if($menu_cur_option==$menu_index-1) {
	    &menu_advice("You are already on the last option!");
	  } else {
	    if($menu_top_option+$menu_pane_lines-3>$menu_index-1) {
	      # the final option is already on screen
	      menu_draw_line($menu_cur_option,$menu_indent);
	      $menu_cur_option=$menu_index-1;
	      &menu_draw_active($menu_cur_option,$menu_indent);
	      &noutrefresh($menu_pane);
	      &menu_advice("");
	    } else {
	      # Need to paint a new screen
	      $menu_top_option=$menu_index-1;
	      $menu_cur_option=$menu_index-1;
	      menu_draw_pane();
	      &menu_advice("");
	    }
	  }
	  &doupdate();
	  last KEYWAIT;
	};
	/RET/ && do {		# button press
	  $action=$menu_button_action[$menu_hot_button];
	  redo KEYWAIT;
	};
	# immediate actions
	/SPACE/ && do {		# Return (enter)
	  $action="STOP";
	  # Togle radio buttons
	  $style=$menu_sel_style[$menu_cur_option];
	DO_STYLE: for ($style) {
	    /2/ && do {
	      for($i=0;$i<$menu_index;$i++) {
		if($menu_sel_style[$i]==2) {
		  $menu_sel_flag[$i]=0;
		}
	      }
	      $menu_sel_flag[$menu_cur_option]=1;
	      &menu_draw_pane();
	      $action="NOP";
	      &doupdate();
	      last DO_STYLE;
	    };
	    /3/ && do {
	      if($menu_sel_flag[$menu_cur_option]==1) {
		$menu_sel_flag[$menu_cur_option]=0;
	      } else {
		$menu_sel_flag[$menu_cur_option]=1;
	      }
	      &menu_draw_active($menu_cur_option,$menu_indent);
	      $action="NOP";
	      &doupdate();
	      last DO_STYLE;
	    };
	    /9/ && do {
	      # Failsafe: should not really get here
	      $action="NOP";
	      redo KEYWAIT;
	    };
	    $menu_sel_flag[$menu_cur_option]=1;
	    last DO_STYLE;
	  };
	  last KEYWAIT;
	};
	/ACCEPT/ && do {		# Return (enter)
	  $action="STOP";
	  if($menu_sel_style[$menu_cur_option]!=3) {
	    if($menu_sel_style[$menu_cur_option]==6) {
	      # edit an alpha field
	      &menu_edit($menu_cur_option,$menu_indent);
	      $action="NOP";
	      &menu_draw_active($menu_cur_option,$menu_indent);
	    }
	    if($menu_sel_style[$menu_cur_option]==7) {
	      # edit a numeric field
	      &menu_edit($menu_cur_option,$menu_indent,1);
	      $action="NOP";
	      &menu_draw_active($menu_cur_option,$menu_indent);
	    }
	  }
	  last KEYWAIT;
	};
	/JUMP/ && do {		# Jump to some option
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/QUIT/ && do {		# Return (enter)
	  $ret="%UP%".$menu_sep;
	  $action="STOP";
	  last KEYWAIT;
	};
	/EXIT/ && do {		# Return (enter)
	  $ret="%EMPTY%".$menu_sep;
	  $action="STOP";
	  last KEYWAIT;
	};
	/HELP/ && do {		# Return (enter)
	  if(-e $menu_help_root.$menu_help) {
	    $help="";
	    open(IN,"<".$menu_help_root.$menu_help);
	    while(<IN>) {
	      $help=$help.$_;
	    }
	    close(IN);
	    &menu_show("Help File ".$menu_help_root.$menu_help,$help,"HELP");
	    # these get switched off by menu_show so do this
	    cbreak();               # permits keystroke examination
	    noecho();               # no input echo until enabled explicitly
	    curs_set(0);            # turn the cursor off
	    &menu_refresh();
	  } else {
	    beep();
	    &menu_advice("Help file ".$menu_help_root.$menu_help." not found");
	    &doupdate;
	  }
	  $action="NOP";
	  last KEYWAIT;
	};
	/REFS/ && do {		# Refresh screen
	  &menu_noutrefresh();
	  &menu_advice("Refreshed Screen");
	  &doupdate();
	  last KEYWAIT;
	};
	# button navigation
	/TAB/ && do {		# Next field
	  &menu_button_bar("TAB");
	  &doupdate();
	  last KEYWAIT;
	};
	/BACK/ && do {		# Previous field
	  &menu_button_bar("BACK");
	  &doupdate();
	  last KEYWAIT;
	};
	# Text editing - not relevant here
	/DEL/ && do {		# Delete right
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/KILL/ && do {		# Kill line
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/YANK/ && do {		# Yank buffer
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/BUFF/ && do {		# Kill buffer
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/INS/ && do {		# insert toggle
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/BS/ && do {		# Backspace
	  menu_advice("$action not defined yet");
	  &doupdate();
	  last KEYWAIT;
	};
	/NOP/ && do {		# Jump to some option
	  #	menu_advice("doing nothing");
	  last KEYWAIT;
	};
	# default: assume a JUMP to a menu option
	# pressing a letter tries to jump to an entry beginning with that
	# letter; really only works for menu styles 0,8 or data fields
	$i=$menu_cur_option+1;
	$new_option=-1;
      CHECK_OPTION: {
	  # Scan next few items for a match
	  $j=ord(uc($action));
	  while($i<$menu_index) {
	    if(ord(uc($menu_sel_label[$i])) == $j) {
	      $new_option=$i;
	      last CHECK_OPTION;
	    }
	    $i++;
	  }
	  # Scan earlier items for a match
	  $i=0;
	  while($i<$menu_cur_option) {
	    if(ord(uc($menu_sel_label[$i])) == $j) {
	      $new_option=$i;
	      last CHECK_OPTION;
	    }
	    $i++;
	  }
	}; # end of option check
	if($new_option>=0) {
	  $menu_cur_option=$new_option;
	  $menu_top_option=$menu_cur_option;
	  &menu_draw_pane();
	  &doupdate();
	}
      };
    } until ($action eq "STOP");

    # We have made our selection so dump the menu windows
    &delwin($menu_pane);
  }
  # curses cookery
  nocbreak();               # permits keystroke examination
  echo();               # no input echo until enabled explicitly
  curs_set(1);            # turn the cursor on
  delwin($menu_window);

  $ret;

}
 
#**********
#  MENU_DRAW_INLAY
#
#  Function:	Draws a menu inlaid box to contain menu options
#		Overlays standard backdrop
#
#  Call format:	&menu_draw_inlay();
#
#  Arguments:   None
#
#  Returns:     Undetermined
#
#**********
sub menu_draw_inlay {

  my @words = ();
  my ($count,$line,$i);

# Draw relief boxes in window
  erase($menu_inlay);
  attrset($menu_inlay,$menu_att_edge);
  addch($menu_inlay,0,0, ACS_ULCORNER);
  hline($menu_inlay,ACS_HLINE, $menu_inlay_cols);

  move($menu_inlay,1,0);
  vline($menu_inlay,ACS_VLINE, $menu_inlay_lines-2);
  addch($menu_inlay,$menu_inlay_lines-1,0, ACS_LLCORNER);
  addch($menu_inlay,$menu_inlay_lines-3,0,ACS_LTEE);
  hline($menu_inlay,ACS_HLINE,$menu_inlay_cols-2);

  attrset($menu_inlay,$menu_att_dull);
  move($menu_inlay,$menu_inlay_lines-1,1);
  hline($menu_inlay,ACS_HLINE, $menu_inlay_cols-2);
  addch($menu_inlay, $menu_inlay_lines-1,$menu_inlay_cols-1,ACS_LRCORNER);

  addch($menu_inlay,0, $menu_inlay_cols-1, ACS_URCORNER);
  move($menu_inlay,1,$menu_inlay_cols-1);
  vline($menu_inlay,ACS_VLINE, $menu_inlay_lines-2);
  addch($menu_inlay,$menu_inlay_lines-3,$menu_inlay_cols-1,ACS_RTEE);

  # Draw the Menu title
  attrset($menu_inlay,$menu_att_title);
  move($menu_inlay,0,($menu_inlay_cols-length($menu_top_title)-2)/2);
  addstr($menu_inlay," $menu_top_title ");

  # Process any sub-titles like the title.
  attrset($menu_inlay,$menu_att_dull);
  if(length($menu_sub_title)>$menu_inlay_cols-4) {
    # Do multi-line subtitle
    @words=split(/ /,$menu_sub_title);
    $menu_sub_title_lines=1;
    $line=$words[0];
    for($i=1;$i<=$#words;$i++) {
      if(length($line." ".$words[$i])<$menu_inlay_cols-4) {
	$line=$line." ".$words[$i];
      } else {
	move($menu_inlay,$menu_sub_title_lines,2);
	addstr($menu_inlay,$line);
	$line=$words[$i];
	$menu_sub_title_lines++;
      }
    }
    move($menu_inlay,$menu_sub_title_lines,2);
    addstr($menu_inlay,$line);
    $line=$words[$i];
  } else {
    # Centre single line subtitle
    move($menu_inlay,1,1+($menu_inlay_cols-length($menu_sub_title)-2)/2);
    addstr($menu_inlay,"$menu_sub_title");
    $menu_sub_title_lines=1;
  }
}


#**********
#  MENU_DRAW_WINDOW
#
#  Function:	Draws a box to actually contain the menu items
#		Overlays standard inlay
#
#  Call format:	&menu_draw_window();
#
#  Arguments:   None
#
#  Returns:     undetermined
#
#**********
sub menu_draw_window {
  my $count;

  # First define window and draw border
  $menu_pane_y=$menu_inlay_y+1+$menu_sub_title_lines;
  $menu_pane_x=$menu_inlay_x+2;
  $menu_pane_lines=$menu_inlay_lines-4-$menu_sub_title_lines;
  $menu_pane_cols=$menu_inlay_cols-4;
  $menu_pane_scroll=($menu_pane_cols/2)-2;

  $menu_window=newwin($menu_pane_lines,$menu_pane_cols,$menu_pane_y,$menu_pane_x);
  bkgd($menu_window,$menu_att_text);
  erase($menu_window);

  # Draw relief boxes in window
  attrset($menu_window,$menu_att_dull);
  addch($menu_window,0,0, ACS_ULCORNER);
  hline($menu_window,ACS_HLINE, $menu_pane_cols-2);

  move($menu_window,1,0);
  vline($menu_window,ACS_VLINE, $menu_pane_lines-2);
  addch($menu_window,$menu_pane_lines-1,0, ACS_LLCORNER);

  attrset($menu_window,$menu_att_edge);
  move($menu_window,$menu_pane_lines-1,1);
  hline($menu_window,ACS_HLINE, $menu_pane_cols-2);
  addch($menu_window, $menu_pane_lines-1,$menu_pane_cols-1,ACS_LRCORNER);

  addch($menu_window,0, $menu_pane_cols-1, ACS_URCORNER);
  move($menu_window,1,$menu_pane_cols-1);
  vline($menu_window,ACS_VLINE, $menu_pane_lines-2);

 }

#**********
#  MENU_DISPLAY
#
#  Function:	Display items in menu_sel_text array, allow selection, and
#		return appropriate selection-string.
#
#  Call format:	$sel = &menu_display("Prompt text",offset);
#
#  Arguments:   - Prompt text (for the bottom line of menu).
#		- start position of item pointer; must be tracked by user
#
#  Returns:     Selected action string (from second param on &menu_item)
#		%UP%    -- quit selected
#               %EMPTY% -- exit selected
#               otherwise gives current item (sometimes followed by
#               tokenised string; always terminated with $menu_sep
#               chop($sel) before use
#               For simple menus just returns selection value
#
#**********
sub menu_display {
  my ($menu_prompt,$menu_start_item) = @_;
  if(!$menu_start_item) {$menu_start_item=0; }

  my ($ret,$i);

  # Diverting to menu_navigate
  $menu_advice=$menu_prompt;             # set default message for this menu
  &menu_advice($menu_prompt);            # show advice
  $menu_top_option=0;
  $menu_cur_option=$menu_start_item;
  $ret=&menu_navigate();

  # Scans through the menu item list looking for items which have been
  # selected (test $menu_sel_flag) This will not work for style 8 items
  # since this field holds the offset value so for these item types
  # only return the current item if it coincides
  #
  # The full returned string may be prefixed with %UP%$menu_sep or %EMPTY%$menu_sep
  # followed by the text_labels of all items selected
  # break this out using split with pattern $menu_sep
  # For data fields, they will all be returned in order of definition
  # whether edited or not
  for($i=0;$i<$menu_index;$i++) {
    if($menu_sel_flag[$i]>0) {
      if(($menu_sel_style[$i]==7)||($menu_sel_style[$i]==6)) {
	$ret=$ret.$menu_sel_return[$i].$menu_sepn.$menu_sel_text[$i].$menu_sep;
      } else {
	$ret=$ret.$menu_sel_return[$i].$menu_sep;
      }
    }
  }
  $ret;
}

#**********
#  MENU_DRAW_LINE
#
#  Function:	Draws a menu item line in appropriate style
#
#  Call format:	$menu_draw_line(menu_item,indent)
#
#  Arguments:   - Menu item : pointer to menu item list
#               - indent from left edge of window (for centreing)
#
#  Returns:     nuffink
#
#**********
sub menu_draw_line {
  my ($m_item,$m_indent)=@_;
  my $i=0;
  my ($numtext);

  # Determine line number
  $i=$m_item-$menu_top_option;

 MENU_STYLE: for ($menu_sel_style[$m_item]) {
    /0/ && do {
      # default - text item
      # Display option text
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_title);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      # Highlight first letter
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_option);
      addch($menu_pane,ord(ucfirst($menu_sel_label[$m_item])));
      last MENU_STYLE;
    };

    /1/ && do {
      # numbered style
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_title);
      $numtext=$m_item+1;
      if(length($numtext)==1) 
	{ $numtext=" ".$numtext; }
      addstr($menu_pane,$numtext);
      last MENU_STYLE;
    };

    /2/ && do {
      # radio button
      move($menu_pane,$i,$m_indent+$menu_item_pos-4);
      attrset($menu_pane,$menu_att_option);
      addch($menu_pane,"[");
      if($menu_sel_flag[$m_item]>0) {
	attrset($menu_pane,$menu_att_title);
	addch($menu_pane,"X");
	attrset($menu_pane,$menu_att_option);
      } else {
	addch($menu_pane," ");
      }
      addch($menu_pane,"]");
      last MENU_STYLE;
    };

    /3/ && do {
      # check list
      move($menu_pane,$i,$m_indent+$menu_item_pos-4);
      attrset($menu_pane,$menu_att_option);
      addch($menu_pane,"(");
      if($menu_sel_flag[$m_item]>0) {
	attrset($menu_pane,$menu_att_title);
	addch($menu_pane,"+");
	attrset($menu_pane,$menu_att_option);
      } else {
	addch($menu_pane," ");
      }
      addch($menu_pane,")");
      last MENU_STYLE;
    };

    /4/ && do {
      # left align data
      # Display option text
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /5/ && do {
      # right align data
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /6/ && do {
      # edit alpha
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /7/ && do {
      # edit numeric
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /8/ && do {
      # a type 0 item but with return data coming from fourth refernce
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /9/ && do {
      # seperator
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };
  }

  if($menu_sel_style[$m_item]==5) {
    # Display item text right aligned
    move($menu_pane,$i,$m_indent+$menu_item_pos+$max_item_len-length($menu_sel_text[$m_item]));
    attrset($menu_pane,$menu_att_text);
    addstr($menu_pane,$menu_sel_text[$m_item]);
  } else {
    # Display item text left aligned (normal)
    move($menu_pane,$i,$m_indent+$menu_item_pos);
    attrset($menu_pane,$menu_att_text);
    addstr($menu_pane,$menu_sel_text[$m_item]);
  }
}

#**********
#  MENU_DRAW_ACTIVE
#
#  Function:	Draws active menu line in reverse style or with an arrow
#
#  Call format:	$sel = &menu_draw_active(menu_item,indent);
#
#  Arguments:   - the entry in the menu item list
#               - how far to indent from the left edge of window
#
#  Returns:     nothing
#
#**********
sub menu_draw_active {
  my ($m_item,$m_indent)=@_;
  my ($i,$numtext);
 
  # Calculate line number
  $i=$menu_cur_option-$menu_top_option;


 MENU_STYLE: for ($menu_sel_style[$menu_cur_option]) {
    /0/ && do {
      # default - text item
      # Display option text
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_rtitle);
      addstr($menu_pane,$menu_sel_label[$menu_cur_option]);
      # Highlight first letter
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_roption);
      addch($menu_pane,ord(ucfirst($menu_sel_label[$m_item])));
      last MENU_STYLE;
    };

    /1/ && do {
      # numbered style
      move($menu_pane,$i,$m_indent);
      attrset($menu_pane,$menu_att_rtitle);
      $numtext=$m_item+1;
      if(length($numtext)==1) 
	{ $numtext=" ".$numtext; }
      addstr($menu_pane,$numtext);
      last MENU_STYLE;
    };

    /2/ && do {
      # radio button
      move($menu_pane,$i,$m_indent+$menu_item_pos-4);
      attrset($menu_pane,$menu_att_rtitle);
      if($menu_sel_flag[$m_item]>0) {
	$numtext="[X]";
      } else {
	$numtext="[ ]";
      }
      addstr($menu_pane,$numtext);
      last MENU_STYLE;
    };

    /3/ && do {
      # check list
      move($menu_pane,$i,$m_indent+$menu_item_pos-4);
      attrset($menu_pane,$menu_att_roption);
      addch($menu_pane,"(");
      if($menu_sel_flag[$m_item]>0) {
	attrset($menu_pane,$menu_att_rtitle);
	addch($menu_pane,"+");
	attrset($menu_pane,$menu_att_roption);
      } else {
	addch($menu_pane," ");
      }
      addch($menu_pane,")");
      last MENU_STYLE;
    };

    /4/ && do {
      # left data
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option|A_BOLD);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /5/ && do {
      # right align data
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option|A_BOLD);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /6/ && do {
      # edit alpha
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option|A_BOLD);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /7/ && do {
      # edit numeric
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option|A_BOLD);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /8/ && do {
      # 
      # Display option text
      move($menu_pane,$i,$m_indent+$menu_item_pos-1-length($menu_sel_label[$m_item]));
      attrset($menu_pane,$menu_att_option|A_BOLD);
      addstr($menu_pane,$menu_sel_label[$m_item]);
      last MENU_STYLE;
    };

    /9/ && do {
      # seperator
      last MENU_STYLE;
    };
  }
  if($menu_sel_style[$m_item]==5) {
    # Display item text right aligned
    move($menu_pane,$i,$m_indent+$menu_item_pos+$max_item_len-length($menu_sel_text[$m_item]));
    attrset($menu_pane,$menu_att_rtext);
    addstr($menu_pane,$menu_sel_text[$m_item]);
  } else {
    # Display item text
    move($menu_pane,$i,$m_indent+$menu_item_pos);
    attrset($menu_pane,$menu_att_rtext);
    addstr($menu_pane,$menu_sel_text[$menu_cur_option]);
  }
}

#**********
#  MENU_CREATE_PANE
#
#  Function:	Create a window within the menu window to display items
#
#  Call format:	&menu_create_pane();
#
#  Arguments:   none
#
#  Returns:     undetermined
#
#**********
sub menu_create_pane {
  # Initialise menu pane and control variables
  $menu_pane=newwin($menu_pane_lines-2,$menu_pane_cols-2,$menu_pane_y+1,$menu_pane_x+1);
  bkgd($menu_pane,$menu_att_text);
  clear($menu_pane);
  idlok($menu_pane,1);
  scrollok($menu_pane,1);
  $menu_top_option=0;
  keypad($menu_pane,1);
  &menu_draw_pane();
}

#*********
# MENU_DRAW_PANE
#
# Function:  Draws the actual menu especially after a keystroke
#            Usually only necessary after big relocations of the menu cursor
#
# Input:     Nothing
#
# Returns:   Nothing 
#
#*********
sub menu_draw_pane {
  my ($i); 

  # Performs test to make sure the items are aligned correctly to use as much of the
  # screen as possible
  if(($menu_top_option+($menu_pane_lines-2))>$menu_index-1) { $menu_top_option=$menu_index-1-($menu_pane_lines-3); }
  if($menu_top_option<0) {$menu_top_option=0; }
  if($menu_cur_option>$menu_index-1) { $menu_cur_option=$menu_index-1; }
  if($menu_cur_option>=($menu_top_option+($menu_pane_lines-2))) {$menu_top_option=$menu_cur_option-($menu_pane_lines-3); }
  if($menu_top_option<0) {$menu_top_option=0; }
  if($menu_cur_option<$menu_top_option) { $menu_cur_option=$menu_top_option; }
  # Now draw the menu items
  clear($menu_pane);
  $i=$menu_top_option;
  while(($i<$menu_index)&&(($i-$menu_top_option)<$menu_pane_lines-2)) {
    &menu_draw_line($i,$menu_indent);
    $i++;
  }
  &menu_draw_active($menu_cur_option,$menu_indent);
  &noutrefresh($menu_pane);
}


#**********
# MENU_KEY_SEQ -- Collect characters until a sequence we recognize (or we
#		  know it cannot possibly fit any "magic" sequences.
#
#  Call format:	$sel = &menu_key_seq();
#
#  Arguments:   none
#
#  Returns:     either a single letter or a key stroke mneumonic for an action to do
#               in some cases it will return nothing if;
#                  - an invalid/unrecognised function key was pressed
#                  - the screen was resized
#               bum values like these should be ignored
#
#**********
sub menu_key_seq {
  my ($cwin) = @_;
  my ($possible,$ch);
  my ($collect,$action) = "";
  my ($resizing)=0;

  $possible = 0;	# Set number of possible matches 

  # Trapping resizing strings waiting for them to stop
 resize_trap:
  do {
    $collect="";
    $action="";
  seq_seek:
    while ($possible <$key_max) {
      $ch = &getch($cwin);
      $collect = $collect.$ch;
      # search for a valid keysequence first
      $action=$kseq{$collect};
      if($action) {
	last seq_seek;
      }
      # Check if just a single letter/number has been pressed
      # This indicates a menu option to jump to
      if(length($collect)==1) {
	# Did we press a SPACE?
	if(ord($collect)==32) {
	  $action="SPACE";
	  last seq_seek;
	}
	$_=$collect;
	if(/\w|\W/i) {
	  $action=$collect;
	  last seq_seek;
	}
      }
      $possible=length($collect);
    }

    # Catch window resizing and loop until resizing appears finished
    # then redraw the whole screen.
    if(($action eq "410")) {
      $resizing=1;
    } else {
      if($resizing==1) {
	#Resizing has occurred but hopefully stopped so change the screen
	&menu_refresh();
	$resizing=0;
      }

    }
  }   while($resizing==1); # end of resize trap

  if((length($collect)>1)&&($action eq '')) {
    &menu_advice("Command $collect not recognised");
    $action="NOP";
  }
  $action;
}

#**********
#  MENU_REFRESH
#
#  Function:	Redraws the whole display usually after a screen resize
#
#  Call format:	&menu_refresh();
#
#  Arguments:   None
#
#  Returns:     nuffink
#
#**********
sub menu_refresh {
  &menu_redraw_backdrop();
  &noutrefresh($menu_screen);
  &menu_draw_inlay();
  &redrawwin($menu_inlay);
  &menu_button_bar();
  &noutrefresh($menu_inlay);
  &delwin($menu_window);
  &menu_draw_window();
  &noutrefresh($menu_window);
  &delwin($menu_pane);
  &menu_create_pane();
  &doupdate();
}

# ##################################################################################
# Data entry routines
# ##################################################################################

#**********
#  MENU_EDIT_ALPHA
#
#  Function:	Edit an alphanumeric field
#
#  Call format:	&menu_edit_alpha();
#               assumes the current item
#
#  Returns:     nuffink
#
#**********
sub menu_edit {
  my ($m_item,$m_indent,$numbers) = @_;
  my ($item_line,$item_col,$item_len,$pos,$dec,$field,$ins,$menu_field,$action,$i);

  $item_line=$menu_pane_y+1+$m_item-$menu_top_option;
  $item_col=$menu_pane_x+1+$m_indent+$menu_item_pos;
  # Get the field information
  ($item_len,$dec) = split(/ /,$menu_sel_pos[$m_item]);

  if($item_len<1) {$item_len=length($menu_sel_text[$m_item]); }
  if(!$item_line) {$item_line=0; }
  if(!$item_col) {$item_col=0; }

  if(!$numbers) {$numbers=0; }

  # Initialise field
  $field=$menu_sel_text[$m_item];
  if(!$field) { $field=""; }         # Make sure something is defined
  $menu_field=newwin(1,$item_len,$item_line,$item_col);
  bkgd($menu_field,$menu_att_backdrop);

  # curses cookery
  curs_set(1);            # turn the cursor off
  $ins=1;                 # turn insert mode on
  $pos=length($field);

  # Now edit the field
  READ_KEY:  do {
      # Collect key sequences until something we recoginize 
      # (or we know we don't care)

      # Format numbers correctly
      if($numbers==1) {
	if(!$field) { $field=0; }
	if($dec==0) {
	  $field=sprintf("%d",$field);
	} else {
	  $field=sprintf("%f",$field);
	}
	if($pos>length($field)) { $pos=length($field); }
      }

      move($menu_field,0,0);
      erase($menu_field);
      addstr($menu_field,$field);
      move($menu_field,0,$pos);
      noutrefresh($menu_field);
      doupdate();

      $action = &menu_key_seq($menu_pane);

      # ------------------------------------------------------------------------------
      # Switch construct for dealing with key sequence input
      # ------------------------------------------------------------------------------
    EDITKEY: for ($action) {
	# General cursor movement
	/DOWN/ && do {		# down arrow
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/UP/ && do {		# Up arrow
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/LEFT/ && do {		# Left arrow
	  $pos--;
	  if($pos<0) {$pos=0; }
	  last EDITKEY;
	};
	/RITE/ && do {		# Right arrow
	  $pos++;
	  if($pos>length($field)) {$pos=length($field); }
	  last EDITKEY;
	};
	# larger cursor motion
	/PREV/ && do {		# Page up
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/NEXT/ && do {		# Page down
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/HOME/ && do {		# Home
	  $pos=0;
	  last EDITKEY;
	};
	/END/ && do {		# End
	  $pos=length($field);
	  last EDITKEY;
	};
	/SPACE/ && do {		# button press
	  $action=" ";
	  redo EDITKEY;
	};
	/RET/ && do {		# button press
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/ACCEPT/ && do {		# Return (enter)
	  $action="STOP";
	  $menu_sel_text[$m_item]=$field;
	  $menu_sel_flag[$m_item]=1;
	  last EDITKEY;
	};
	/QUIT/ && do {		# Return (enter)
	  $action="STOP";
	  last EDITKEY;
	};
	/EXIT/ && do {		# Return (enter)
	  $action="STOP";
	  last EDITKEY;
	};
	/HELP/ && do {		# Return (enter)
	  menu_advice("$action not defined yet");
	  last EDITKEY;
	};
	/REFS/ && do {		# Refresh screen
	  &menu_advice("Refreshed Screen");
	  last EDITKEY;
	};
	# button navigation
	/TAB/ && do {		# Next field
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/BACK/ && do {		# Previous field
	  $action="ACCEPT";
	  redo EDITKEY;
	};
	/BS/ && do {		# Home
	  if($pos!=0) {
	    $field=substr($field,0,$pos-1).substr($field,$pos);
	    $pos--;
	    if($pos==0) {$pos=0; }
	  }
	  last EDITKEY;
	};
	/DEL/ && do {		# Delete right
	  if($pos<length($field)) {
	    $field=substr($field,0,$pos).substr($field,$pos+1);
	    if($pos>length($field)) {$pos=length($field); }
	  }
	  last EDITKEY;
	};
	/KILL/ && do {		# Kill line
	  $field="";
	  $pos=0;
	  &menu_advice("Field cleared");
	  last EDITKEY;
	};
	/YANK/ && do {		# Yank buffer
	  $field=$buffer;
	  $pos=length($field);
	  &menu_advice("Field recalled from buffer");
	  last EDITKEY;
	};
	/BUFF/ && do {		# Copy to buffer
	  $buffer=$field;
	  &menu_advice("Field copied to buffer");
	  last EDITKEY;
	};
	/INS/ && do {		# insert toggle
	  if($ins==1) {
	    &menu_advice("Overwrite mode On");
	    $ins=0;
	  } else {
	    $ins=1;
	    &menu_advice("Insert mode On");
	  }
	  last EDITKEY;
	};
	/NOP/ && do {		# Jump to some option
	  last EDITKEY;
	};
	# deal with a letter press or unknown key
	if(length($action)==1) {
	  if(($numbers==1) && (index("0123456789+-.",$action)<0)) {
	    # check for numeric only input
	    beep();
	    last EDITKEY;
	  }
	  study($field);
	  if($ins==1) {
	    # insert a character
	    if(length($field)>=$item_len) {
	      # ignore if field already full
	      beep();
	      last EDITKEY;
	    }
	    $i=substr($field,0,$pos).$action.substr($field,$pos);
	    $pos++;
	    if($pos>=$item_len) {$pos--; }
	  } else {
	    # replace text (overwrite)
	    $i=substr($field,0,$pos).$action.substr($field,$pos+1);
	    if($pos==length($field)) {$pos++; }
	    if($pos>=$item_len) {$pos--; }
	  }
	  $field=$i;
	}
      }; # end of option check
    } until ($action eq "STOP");

  # return screen to normal after field edit
  curs_set(0);            # turn the cursor off
  delwin($menu_field);
  move($menu_pane,$m_item-$menu_top_option,$m_indent);
  clrtoeol($menu_pane);
  &noutrefresh($menu_window);
}


# ##################################################################################
# ***************************************************************************
# Button Bar
# ~~~~~~~~~~
# A button bar can appear at the foot of each Menu. Button labels are
# user definable using the menu_button_set function
# Buttons perform
#   ACTION - select the current menu option
#   HELP   - display user provided help information
#   EXIT   - exit back from the current menu
# These functions are pre-set
# ***************************************************************************
# ##################################################################################

#**********
#  MENU_BUTTON_SET
#
#  Function:	Sets the text to be displayed in the 3 standard buttons
#
#  Call format:	&menu_button_set(button[0|1|2|3],"Button text");
#
#  Arguments:   - which button to affect 0=None 1=Okey 2=Help 3=Exit
#                 Setting of None causes messages to be shown in the
#                 button bar with the text used as default text
#               - Textual content of the button
#                 If content is empty the button will be switched off
#                 If all buttons are off mode changes as if NONE selected
#
#  Notes:       User code must keep track of what is going on, we don't
#**********
sub menu_button_set {
  my ($button,$button_text) = @_;
  my ($i);

  $menu_button[$button]=$button_text;

  # FAILSAFE: Check that some buttons are on
  $menu_buttons=0;
  for($i=1;$i<=3;$i++) {
    if(length($menu_button[$i])>0) {
      $menu_buttons++; 
    }
  }
}

#**********
#  MENU_BUTTON_BAR
#
#  Function:	Bounces the active button right or left
#
#  Call format:	&menu_button_bar(action);
#
#  Arguments:   action can be either TAB or BACK which bounces the active
#               button in the chosen direction. Bouncing wraps at both edges
#
#  Returns:     lateral thinking
#**********
sub menu_button_bar {
  my ($tab) = @_;
  my ($i,$j,$x);
  my (@b);

  # Change the active button
 ACTIVE_BUTTON: for ($tab) {
    /TAB/ && do {
      do {
	$menu_hot_button++;
	if($menu_hot_button>3) { $menu_hot_button=1; }
      } until(length($menu_button[$menu_hot_button])>0);
      last ACTIVE_BUTTON;
    };
    /BACK/ && do {
      do {
	$menu_hot_button--;
	if($menu_hot_button<1) { $menu_hot_button=3; }
      } until(length($menu_button[$menu_hot_button])>0);
      last ACTIVE_BUTTON;
    };
  };

  # Calculate position of buttons
 DO_BUTTONS: for ($menu_buttons) {
    /1/ && do {
      $b[1]=$menu_inlay_cols/2;
    };
    /2/ && do {
      $b[1]=$menu_inlay_cols/3;
      $b[2]=$menu_inlay_cols-$b[1];
    };
    /3/ && do {
      $b[1]=$menu_inlay_cols/4;
      $b[2]=$menu_inlay_cols/2;
      $b[3]=$menu_inlay_cols-$b[1];
    };
  }

  # clear the button bar and redraw it as required
  $j=1;
  for($i=1;$i<=3;$i++) {
    $x=length($menu_button[$i]);
    if($x!=0) {
      if($menu_hot_button==$i) {
	&menu_hot_button($b[$j]-1-$x/2,$menu_button[$i]);
      } else {
	&menu_draw_button($b[$j]-1-$x/2,$menu_button[$i]);
      }
      $j++;
    }
  }
  &noutrefresh($menu_inlay);
  &doupdate();
}

# Draw the active button
sub menu_hot_button {
  my ($h_indent,$text) = @_;
  my ($cap);
  
  # Pick out the capital letter
  $cap=ord(uc($text));
  &attrset($menu_inlay,$menu_att_rtext);
  &move($menu_inlay,$menu_inlay_lines-2,$h_indent);
  addstr($menu_inlay,"<");
  &attrset($menu_inlay,$menu_att_rtitle);
  addstr($menu_inlay,$text);
  &attrset($menu_inlay,$menu_att_rtext);
  addstr($menu_inlay,">");
  &attrset($menu_inlay,$menu_att_roption);
  &move($menu_inlay,$menu_inlay_lines-2,$h_indent+1);
  addch($menu_inlay,$cap);
}

# Draw an inactive button
sub menu_draw_button {
  my ($h_indent,$text) = @_;

  &attrset($menu_inlay,$menu_att_button);
  &move($menu_inlay,$menu_inlay_lines-2,$h_indent);
  addstr($menu_inlay,"<");
  addstr($menu_inlay,$text);
  addstr($menu_inlay,">");
}

# ##################################################################################
# Splash screen for Popups and Text displays
# ##################################################################################

#**********
#  MENU_POPUP
#
#  Function:	Pops up a single line text message
#               Can be used to keep users interested while a lengthy process
#               completes; popup remains on screen until destroyed by
#               calling the routine again with no message
#
#  Call format:	&menu_popup(message,title);   # create a popup
#               &menu_popup();                # destroy popup
#
#  Arguments:   - message - a text message to be displayed;
#                 should be single line only, will be truncated if too long
#                 If the message is empty an old popup will be destroyed
#               - title - title centred in the popup border
#                 defaults to "processing" if not provided
#
#  Returns:     nothing
#               
#  Notes:       popup may appear over a blank screen since the menu windows
#               may have been removed (not guaranteed)
#**********
sub menu_popup {
  my ($message,$ptitle) = @_;

  if(!$ptitle) {$ptitle="processing"; }

  &menu_advice(" ");

  if(!$message) {
    # no message so destroy the old popup
    # curses cookery
    echo();               # no input echo until enabled explicitly
    curs_set(1);            # turn the cursor on
    &delwin($menu_popup);
    &menu_redraw_backdrop();
  } else {
    # create a new popup
    noecho();               # no input echo until enabled explicitly
    curs_set(0);            # turn the cursor off
  
    while(length($message)>$menu_screen_cols-8) {
      chop $message;
    };
    # Initialise menu pane and control variables
    $menu_popup=newwin(3,$menu_screen_cols-6,($menu_screen_lines/2)-2,3);
    bkgd($menu_popup,$menu_att_popup);
    clear($menu_popup);
    &border($menu_popup,0,0,0,0,0,0,0,0);
    move($menu_popup,0,($menu_screen_cols-8-length($ptitle))/2);
    addstr($menu_popup," $ptitle ");

    move($menu_popup,1,($menu_screen_cols-6-length($message))/2);
    addstr($menu_popup,$message);

    &refresh($menu_popup);
    &doupdate();
  }
}

#**********
#  MENU_SHOW
#
#  Function:	Pops up a text message with a button bar
#               Used as a user confirmation advice before a process
#               is performed
#               Button bar defined according to current button settings
#               Help screen is called directly
#
#  Call format:	&menu_show(message);
#
#  Arguments:   - message - a text message to be displayed;
#                 can be multiline (left-just) or single (centred), choice
#                 depends on size of window
#
#  Returns:     a simple string either YES or NO
#               
#  Notes:       Uses Text::Wrap to fill the window if the text to be shown
#               is longer than a line - this allows some formatting to be 
#               performed
#                  \n   forces a line break
#               Check docs for Text::wrap for more info
#               At present only fills to current window depth and will lose
#               any additional text; no scrolling supported yet
#               
#               Useful as a debugging tool for your own scripts to see
#               what is going one since "print" will not work under
#               Curses. 
#               
#**********
sub menu_show {
  my ($temp_title,$message,$colour) = @_;
  my ($attributes,$work,$x,$i,$j);
  my ($menu_popup);
  my (@b);

  &menu_advice(" ");

  if(!$colour) { $colour="ERROR"; }
 SET_COLOR: for ($colour) {
    /WARN/ && do {
      $attributes=$menu_att_warn;
    };
    /HELP/ && do {
      $attributes=$menu_att_help;
    };
    /ERROR/ && do {
      $attributes=$menu_att_error;
    };
  };

  if(!$message) {
    # no message given so ignore the call
    return("NO");
  } else {
    # create a popup with button bar
    bkgd($menu_inlay,$attributes);
    erase($menu_inlay);
    &border($menu_inlay,0,0,0,0,0,0,0,0);
    move($menu_inlay,0,($menu_inlay_cols-length($temp_title)-2)/2);
    addstr($menu_inlay," $temp_title ");

    &noutrefresh($menu_inlay);
    # Initialise menu pane and control variables
    # First define window and draw border
    $menu_pane_y=$menu_inlay_y+1;
    $menu_pane_x=$menu_inlay_x+1;
    $menu_pane_lines=$menu_inlay_lines-3;
    $menu_pane_cols=$menu_inlay_cols-2;
    
    &noutrefresh($menu_inlay);

    $menu_popup=newwin($menu_pane_lines-2,$menu_pane_cols-2,$menu_pane_y,$menu_pane_x+1);
    bkgd($menu_popup,$attributes);
    erase($menu_popup);

    # curses cookery
    cbreak();               # permits keystroke examination
    noecho();               # no input echo until enabled explicitly
    curs_set(0);            # turn the cursor off
  
    if(length($message)<$menu_pane_cols) {
      move($menu_popup,$menu_pane_lines/2,($menu_pane_cols-length($message))/2);
      addstr($menu_popup,$message);
      &refresh($menu_popup);
    } else {
      $Text::Wrap::columns=$menu_pane_cols-2;
      addstr($menu_popup,wrap("","",$message));
      &refresh($menu_popup);
    }
    if($colour eq "HELP") {
      # We have to do this since HELP can be called while in a menu
      # when menu_show would otherwise trash the button labels
      move($menu_inlay,$menu_inlay_lines-2,($menu_inlay_cols/2)-7);
      &attrset($menu_inlay,$attributes|A_BOLD);
      addstr($menu_inlay,"<Press any Key>");
      &refresh($menu_inlay);
      getch($menu_inlay);
    } else {
      do {
	# Calculate position of buttons
      DO_BUTTONS: for ($menu_buttons) {
	  /1/ && do {
	    $b[1]=$menu_inlay_cols/2;
	  };
	  /2/ && do {
	    $b[1]=$menu_inlay_cols/3;
	    $b[2]=$menu_inlay_cols-$b[1];
	  };
	  /3/ && do {
	    $b[1]=$menu_inlay_cols/4;
	    $b[2]=$menu_inlay_cols/2;
	    $b[3]=$menu_inlay_cols-$b[1];
	  };
	}
	# Draw buttons
	$j=1;
	for($i=1;$i<=3;$i++) {
	  $x=length($menu_button[$i]);
	  if($x>0) {
	    # Draw Okay button
	    if($menu_hot_button==$i) {
	      # Make it hot
	      move($menu_inlay,$menu_inlay_lines-2,$b[$j]-($x/2)-1);
	      &attrset($menu_inlay,$attributes|A_BOLD);
	      addstr($menu_inlay,"<$menu_button[$i]>");
	    } else {
	      # make it cool
	      move($menu_inlay,$menu_inlay_lines-2,$b[$j]-($x/2)-1);
	      &attrset($menu_inlay,$attributes);
	      addstr($menu_inlay,"<$menu_button[$i]>");
	    }
	    $j++;
	  }
	}
	&refresh($menu_inlay);
	$work=&menu_key_seq($menu_inlay);
      CONFIRM: for ($work) {
	  /TAB/ && do {
	    $menu_hot_button++;
	    do {
	      $menu_hot_button++;
	      if($menu_hot_button>3) { $menu_hot_button=1; }
	    } until (length($menu_button[$menu_hot_button])>0);
	    $work="";
	    last CONFIRM;
	  };
	  /BACK/ && do {
	    do {
	      $menu_hot_button--;
	      if($menu_hot_button<1) { $menu_hot_button=$menu_buttons; }
	    } until (length($menu_button[$menu_hot_button])>0);
	    $work="";
	    last CONFIRM;
	  };
	  /RET/ && do {
	    if($menu_hot_button==1) { $work="YES"; }
	    if($menu_hot_button==2) { $work="HELP"; }
	    if($menu_hot_button==3) { $work="NO"; }
	    last CONFIRM;
	  };
	  $work="";
	};
      } until ($work ne "");
    }
    # curses cookery
    nocbreak();               # permits keystroke examination
    echo();               # no input echo until enabled explicitly
    curs_set(1);            # turn the cursor on
    &delwin($menu_popup);
    &menu_redraw_backdrop();
  }
  $work;
}


# ##################################################################################
# End of Module
# ##################################################################################

1;

__END__
# Below is the stub of documentation for your module. You better edit it!

=head1 NAME

Cmenu - Perl extension for menuing and data entry in perl scripts

=head1 SYNOPSIS

  use Cmenu;
  use Curses;
  use Text::Wrap;

  &menu_initialise($main_title,$advice);
  &menu_init($title,$sub-title,$topest,$menu_help);
   &menu_item($item_text,$item_label,$item_style,$item_data,$item_pos)
   &menu_item($item_text,$item_label,$item_style,$item_data,$item_pos)
    ...
   &menu_item($item_text,$item_label,$item_style,$item_data,$item_pos)

  $sel=&menu_display($advice,$start_item);

  &menu_button_set($button,$button_text);

  &menu_popup($title,$text);
   ...
  &menu_popup();

  &menu_show($title,$text,$colour);

  &menu_terminate($message);

=head1 DESCRIPTION

CMENU is a Perl Module designed to provide functions for the
creation of menus in perl scripts.

It follows on from perlmenu but uses a Curses interface for
screen manipulation. It also uses the Text::Wrap module to
process large chunks of text for display. These two modules
should be loaded by user scripts.

The sequence of menu processing is as follows;
  1. Initialise the module
    loop
      2. Define a menu structure 
      3. Define several menu options
      4. Call the menu
      5. Deal with the menu selections
    loop
  6. Terminate the module

The module also provide some extra functions.


=head2 menu_initialise

This routine initialises Curses and creates necessary structures
for the menu module. It accepts two parameters which may be empty;
  $main_title  A script-wide title displayed on all pages
  $advice      A short text advisory displayed at the foot
               of every screen (unless over-ridden by the
               module).
The routine returns nothing.

=head2 menu_init

The routine creates a graphic backdrop in the style of the 
command-line utility "dialog". It accepts 3 parameters
  $title        a menu title displayed at the top
  $sub_title    sub-title text used to give more description
  $menu_help    a help-file to be displayed when the Help key
                is pressed. The help file is located in a
                standard location as defined in the configuration
                file. (optional)

=head2 menu_item

Each line of a menu is created using this call.
  $item_text    The text to be displayed as the menu option
  $item_label   A text label which may be displayed beside
                the text
  $item_style   How the menu option should be drawn or nehave
                Should be a number form 0 to 9
       0  (default) preceeds the text with a text label
          the label is returned if the item is selected
       1  use number instead of a text label; numbered in
          order of definition
       2  item is part of a radio list; radio lists allow
          only ONE item to be selected per menu
       3  item is part of a check list; check lists allow
          any number (inc. none) of items to be selected
       4  as for type 0 expect item label is rendered differently
          usually used to list data fields where the text is
          the contents of a field and the label is its meaning
       5  as for 4 except the item text is right-aligned
       6  as for 4 but if the item is selected, field contents
          can be edited
       7  as for 6 except field treated as a numeric value
       8  displayed as for 4 except an alternative reference
          (not the text label) is returned when selected
       9  spacer; leaves a space in the menu
   $item_data    Some item styles need extra information
       2  which item in a radio list is already active
       3  item in a check list already selected
       6  specifies the return value for the field
       7  as for 6
       8  as for 6
   $item pos     For edit fields only (6 + 7); specifies the
                 maximum length of a data field and decimal 
                 precision for numbers. Passed as a space
                 seperated list eg "30 2 0", length 30 with
                 2 decimal places

=head2 menu_display

Actually performs the menu display and navigation. Returns 
information relevant to the action selected. Accepts 3 parametrs;

  $menu_prompt   Displayed at the foot of the screen as advice
  $menu_start    Which item should be active from the start
                 This allows items other than the first declared
                 to be selected; useful when returning to a menu
                 after an earlier selection (optional)

This is the important call which returns the result of menu
navigation. Depending on the style of menu items defined, various results
will be returned. Generally all selections are a tokenised list seperated
by a standard character ($Cmenu::menu_sep - can be changed by user). For
simple menus, only the selected text label (0,1,4,5) or offset (8) will be
returned.

For radio and check lists (2 and 3) all the selected items will be returned
using each items text label

For edited data fields, more complex values are returned. All editable fields
on a menu will have a token (whether edited or not) returned. Each token has two
fields - the field label and the new field contents; these are seperated by
$Cmenu::menu_sepn.

Since any type of item can be included in a menu, return values may be
equally complex. For complex return values, tokens can be split out using
a command fragment such as

 chop($return_value=&menu_display("Menu Prompt","Start Menu Item"));
 @selection=split(/$Cmenu::menu_sep/,$return_value);
 for($loop=1;$loop<=$#selection;$i++) {
   # deal with each token
   ($field_label,$field_content) = split(/$Cmenu::menu_sepn,$selection[$i]);
   # processing each field accordingly
   ...
   }

The first token returned is usually the menu item which was active when the
menu was closed; this may not always be a valid item if the menu has been aborted

=head2 menu_button_set

Each menu has up to 3 buttons which can be applied. Usually these give
options to either Accept a menu item or Abort the menu prematurely. A Help
facility may also be called.

This routine switches buttons on and off and specifies the text label of the button
(button actions cannot be altered yielding "ACCEPT", "HELP" or "EXIT"). The <TAB> key
traverses these buttons.

Parameters for this routine are;
  $button  a number 1, 2 or 3 specifying which button is to be set
  $label   the text label for the button; an empty string switches the button off

=head2 menu_popup

Allows a simple screen to popped up if a lengthy process has been launched. The popup
has only one line of text to give an indication of what the system is doing; 
  To start a popup - call with $message
  To close a popup - call with NO message

=head2 menu_show

Allows a variety of information to be shown on the screen; the display
generally replaces normal menu rendering until the user presses an approriate key.
The routines takes 3 parameters
  $title    the title of the display
  $message  the message to be displayed. If this is only one line it will be
            centred; if longer the external routine Text::wrap is used to
            manipulated the text to fit on the screen. Text formatting
            is quite primitive.
            The display cannot be scrolled if it exceeds the dimensions of
            the active window
  $colour   colour style to render the display chosen from HELP|WARN|ERROR
            HELP screens have an automatic button to continue; WARN and ERROR 
            can have multiple buttons (use menu_button_set to control these)

=head2 menu_terminate

Called as the script terminates to close down menu facilities and Curses.
The terminal should be left in a sane state. The $message parameter prints
to STDOUT as the script/routine finishes.

If a scripts aborts before calling this, the sanity of the tty will likely
get lost; use the command "reset" to restore sanity.

=head1 AUTHOR

Andy Ferguson andy@moil.demon.co.uk

=head1 FILES

cmenurc  configuration file to set terminal and screen defaults
          this file may be
             System Wide   - in /etc/Cmenu/.cmenurc
             User specific - ~/.cmenurc
             Run Specific  - ./cmenurc
          See the distributed file for contents.

vt100-wy60 A tic (terminfo) file for VT100 emulation on a Wyse 60
          terminal; this sets the functions keys appropriately

demo      A sample script showing how menus can be rendered with the module.

=head1 BUGS

* No continuation pages or checks for text displays overflowing the windows.
* Resize and Refresh functions can misbehave in spawned shells
* BACKTAB definition from Curses is lost so can only TAB forwards thru buttons

perl(1).

=cut
