From: skunz@iastate.edu (Steven L Kunz)
Subject: menu.pl version 1.1
Summary: perl menu package
Organization: Iowa State University, Ames IA
Date: Thu, 27 Aug 1992 22:37:25 GMT

A few months ago I posted a perl package to do curses-based menu functions.
This package uses "curseperl" (in the usub directory of the perl distribution).
The version I posted was my first (development) version and was menu.pl
version .9 (beta).  Since then the routines have been tweeked a bit and
this is the current release (1.1).  Best documentation is the "menu.pl"
package itself - look at the front of each routine.  

Comments/bugs welcome.

Steve Kunz (skunz@iastate.edu)

------------------------- menu.pl package follows ------------------------
#!/bin/sh
# to extract, remove the header and type "sh filename"
if `test ! -s ./README`
then
echo "writing ./README"
cat > ./README << '\End\Of\Shar\'
                                   menu.pl
                                 Perl Menus
                                 Version 1.1

                               Steven L. Kunz
                         Networking & Communications
                  Iowa State University Computation Center
                            Iowa State University
                                 Ames,  Iowa
                  

This is "menu.pl" - a set of perl routines that will perform full screen
menu functions using "curseperl".  What you should have after unpacking 
this package is the following:

  README         (this file)

  RELEASE_NOTES  Differences between this version and previous versions.

  demo           A simple menu demo.

  ezview         A more involved demo showing how menus can be used to call
                 routines, select files, etc.

  menu.pl        The perl menu subroutines in a package (usually placed 
                 somewhere like /usr/local/lib/perl/menu.pl)

  ultpatch       Patches I had to apply to the bsdcurses.mus file to make 
                 it work on an ULTRIX (BSD based) system.

Installation:

  1) If you don't have curseperl working and installed somewhere, go into
     your perl.4.35 distribution (in the "usub" directory) and construct it
     following the instructions there.  Be forewarned that when I put it 
     together it didn't work on my ULTRIX 4.2a system - I had to install some
     patches I got off comp.lang.perl (posted by drw@nevanlinna.mit.edu)
     and some changes I had to add myself.  The file "ultpatch" is the
     diffs between what I run and what is distributed with perl.  Patch
     your original bsdcurses.mus with this if you are having trouble
     getting curseperl to work with ULTRIX.

     You will probably have to modify the first lines of both demo scripts
     (demo and ezmail) to point to where your curseperl is.

  2) Put "menu.pl" with the rest of your perl packages (usually in something
     like "/usr/local/lib/perl").  The demo programs will work just leaving
     in the same directory as the demo/ezview scripts.

Use:

  Construct your applications to use "curseperl" instead of "perl".

  There are five calls to use perl menus - usually you will only use the
  following three:

     &menu_init(1,"title"); # Init menu

     &menu_item("Selection text 1","return_string_1"); # Add item
     &menu_item("Selection text 2","return_string_2"); # Add item
     ...
     &menu_item("Selection text n","return_string_n"); # Add last item

     $sel = &menu_display("Prompt text"); # Get user selection

  The "menu_init" call resets the menu array and indexes.  It must be called
  to reset/clear any old menu.  The first parameter is a boolean flag 
  indicating whether or not the menu should be numbered and have a selection
  arrow "->" provided (a non-zero value indicates a numbered menu).
  Unnumbered menus provide a nice way of paging text files - see the 
  "ezview" demo file for an example (in the "view_file" routine).
  If the first character of the title is a dash ("-") then the title will
  not be presented in "standout" rendition.

  The "menu_item" call provides selection text (what the user sees on the
  screen) and "action_text" (not seen - but returned if that item is
  selected).  There is no practical limit (other than memory or maximum
  array index size) of the number of items in a menu.  The items are
  presented in the order you add them and the top (first) item is always
  the default.

  The "menu_display" call is the only call that actually writes data on the
  screen.  When it returns you either have the string "%UP%" (indicating the
  user did not select anything but pressed "u" to exit the menu), "EMPTY%
  (indicating no calls were made to "menu_item"), or you have one of the
  selection-action strings given on a "menu_item" call.  You can either
  provide your own prompt as a call parameter to "menu_display"  or you can
  provide a null string (&menu_display("")) in which an automatic prompt is
  provided.  All paging functions are handled within the call to
  "menu_display" automatically.  Note that support is provided for just simply
  typing the selection number of the items on the screen - you do not have to
  move the selection arrow to the item if you prefer to type the number
  (followed by "return"). 

  It is assumed that the application calling the menu routines is not a
  "curseperl" application (i.e. it is a "stock" perl script).  However,
  if you are writing an "all-curses" application you should call
  "menu_curses_application" FIRST (once).  This sets a flag so that the
  "initscr" and "endwin" calls are NOT done by the menu.pl package calls
  (and assumes you will do them).  

  The menu routines will process a "q" for "quit" locally.  In other words,
  if the user presses "q" while a menu is displayed (and responds to the
  "Do you really want to quit?" prompt with a "y") the perl program will
  immediately exit.  However, support is provided for a "user" exit that
  will be called just before dropping out the program (to perform any
  "cleanup" duties).  Calling "&menu_quit_routine("rtn_name");" will set
  the exit routine.
   
General notes:
  If you are running my demos make sure the first line points to where
  your "curseperl" is and the "require" line looks in the right place
  (if in a non-standard library).

  Also note that the call to menu_display will ALWAYS turn back on echo - so
  if you really want it off you will have to call noecho again after each
  menu_display.

---
Steven L. Kunz
Networked Applications
Iowa State University Computation Center, Iowa State University, Ames  IA
INET: skunz@iastate.edu     BITNET: gr.slk@isumvs.bitnet
\End\Of\Shar\
else
  echo "will not over write ./README"
fi
if `test ! -s ./RELEASE_NOTES`
then
echo "writing ./RELEASE_NOTES"
cat > ./RELEASE_NOTES << '\End\Of\Shar\'

Changes between menu.pl version .9 (beta) and version 1.1:

- Declaration of "curses_application" fixed ("main`" prepended).
- Cleanup of "cbreak" and "echo" handling.  Calls to "menu_display"
  always return with "echo" and "cbreak" set.
- Return key now functions on systems that do not have termcap entries
  for either a "newline" or "return" key.
- "menu_display" will return "%EMPTY%" if no calls to "menu_item" were
  done between a "menu_init" call and a "menu_display" call.
- Hitting the "space bar" is now the same as "f" or "n" for forward movement
  within a multi-page selection menu.
- The title strings in "menu_init" calls can now begin with a "-" to suppress
  the "standout" attribute (normally a bold or reverse-video rendition).
- menu_display will no longer return "%QUIT%" - returns "%UP" instead.  The
  menu routines process a "q" (for "quit") locally and will exit from
  there (after the user responds to a "Do you really want to quit?" prompt).
- Direct number entry for selecting entries "pops" the arrow to the 
  "best fit" selection on the screen, indicating what selection will be 
  made when return is hit.

- The "ezview" demo now displays the correct modification date on its file
  display.
\End\Of\Shar\
else
  echo "will not over write ./RELEASE_NOTES"
fi
if `test ! -s ./demo`
then
echo "writing ./demo"
cat > ./demo << '\End\Of\Shar\'
#!../bin/curseperl
#
# demo --  Simple perl menu demo
#
# Note:    Requires curseperl
#
# Author:  Steven L. Kunz
#          Networking & Communications
#          Iowa State University Computation Center
#          Ames, IA  50011
#          Email: skunz@iastate.edu
#
# Date:    May, 1992
#

require "menu.pl";

  &test_short_menu;
  &test_long_menu;
  exit;

#
#  Build a short (one page) demo menu.
#
sub test_short_menu {

# Init a numbered menu with a title
  &menu_init(1,"Short Menu (fits on one page)");

# Add several items
  &menu_item("Dog","animal");
  &menu_item("Cat","animal");
  &menu_item("Granite","mineral");
  &menu_item("Mouse","animal");
  &menu_item("Shale","mineral");
  &menu_item("Onion","vegetable");
  &menu_item("Carrot","vegetable");

# Get selection
  $sel= &menu_display("");

  if ($sel eq "%UP%") {
    exit;
  }

  print "You picked a $sel.\n";
  $ch = <>;
}

#
# Build demo long menu (several pages)
#
sub test_long_menu {
  local($sel_num);

# Init a numbered menu with title
  &menu_init(1,"Long Menu (fits on several pages)");

# Build 50 entries in the menu
  $i = 0;
  while ($i < 50) {
    $sel_num = $i + 1;
    &menu_item("Item $sel_num","action-$sel_num");
    $i++;
  }

# Get user selection
  $sel = &menu_display("");

  if ($sel eq "%UP%") {
    exit;
  }

  print "You picked the item with selection-action $sel.\n";
  $ch = <>;
}
\End\Of\Shar\
else
  echo "will not over write ./demo"
fi
if `test ! -s ./ezview`
then
echo "writing ./ezview"
cat > ./ezview << '\End\Of\Shar\'
#!../bin/curseperl
#
# EasyView -- Unix File Viewer/Editor Interface
#             (a "practical" demo for menu.pl)
#
# Note:    Requires curseperl
#
# Author:  Steven L. Kunz
#          Networking & Communications
#          Iowa State University Computation Center
#          Ames, IA  50011
#          Email: skunz@iastate.edu
#
# Date:    May, 1992
#

require "menu.pl";

    $lines = $LINES; $lines1 = $lines - 1; $lines2 = $lines - 2;
    $cols = $COLS;   $cols1  = $cols  - 1; $cols2  = $cols  - 2;;

    $SIG{'INT'} = 'cleanup';
    $| = 1;		# command buffering on stdout

#
#  MAIN_MENU -- Main (top level) menu
#
  while (1) {
    &menu_init(1,"EasyView Version 1.1");
    &menu_item("Exit","%UP%");
    &menu_item("List files in current directory","dir_list");
    &menu_item("View a text file","view_file");
    &menu_item("Edit a text file","edit_file");

    $subr=&menu_display("");
    if ($subr eq "%UP%") { 
      &cleanup;
    }
    if ($subr ne "") { &$subr; }	# Call subroutine selected
  }

#**********
#  DIR_LIST -- Provide directory list
#**********
sub dir_list {
   &dir_select(0,".","Directory Contents");
}

#***********
#  VIEW_FILE -- View a file in the current directory
#
#  Arguments:  None
#
#  Returns:    Nothing
#
#  Note: Uses file as an unnumbered menu
#***********
sub view_file {
  local($filename);

# Call utility function to select file
  $title = "Select file to view";
  $filename = &dir_select(1,".","Select file to page through");
  if ($filename eq "%UP%") {
    return;
  }

# Load file as an unnumbered menu - let menu_display do the paging
#
# Special thanks: The tab expansion used here was lifted from the
# "pager" program distributed with perl.4.19 in the "usub" directory.
# Don't know who wrote it but it fit the bill.  SLK
#
  &menu_init(0,"File: $filename");
  open(TEMP,$filename);
  while (<TEMP>) {
    s/^(\t+)/'        ' x length($1)/e;
    &expand($_) if /\t/;
    &menu_item($_,"");
  }
  &menu_display("");
  close(TEMP);
}

sub expand {
    while (($off = index($_[0],"\t")) >= 0) {
        substr($_[0], $off, 1) = ' ' x (8 - $off % 8);
    }
}


#***********
#  EDIT_FILE -- Edit a file in the current directory
#***********
sub edit_file {
  &clear;
  $title = "Select file to edit";
  $filename = &dir_select(1,".","Select file to edit");
  if ($filename eq "%UP%") {
    return;
  }
  system("vi $filename");
}

#************
#  DIR_SELECT -- Load a formatted directory list into a menu.
#
#  Arguments:  Boolean flag indicating numbered menu (1=yes), directory 
#              name string and top-title string for menu
#
#  Returns:    File name (or "%UP%")
#************
sub dir_select {
  local($numbered,$directory,$title) = @_;
  opendir(DIR,$directory);
  &menu_init($numbered,$title);
dir_entry:
  while ($filename = readdir(DIR)) {
    next dir_entry if ($filename eq "."); 
    next dir_entry if ($filename eq ".."); 
    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,
     $blksize,$blocks) = stat($filename);
    ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($mtime);
    $mon++;
    $sel_action = $filename;
    $filename=$filename.substr("                    ",0,20-length($filename));
    $sel_text = sprintf("%s%s%02d/%02d/%02d %02d:%02d  %s\n",
                $filename,$filler,$mon,$mday,$year,$hour,$sec,getpwuid($uid));
    &menu_item($sel_text,$sel_action);
  }
  $fn = &menu_display("");
  $fn;
}

sub cleanup {
  &clear;
  &refresh;
  exit;
}
\End\Of\Shar\
else
  echo "will not over write ./ezview"
fi
if `test ! -s ./menu.pl`
then
echo "writing ./menu.pl"
cat > ./menu.pl << '\End\Of\Shar\'
#****************************************************************************
# menu.pl -- Perl Menu Support Facility 
#
# Version: 1.1
#
# Author:  Steven L. Kunz
#          Networking & Communications
#          Iowa State University Computation Center
#          Ames, IA  50011
#
# Bugs:    skunz@iastate.edu
# Cheers:  skunz@iastate.edu
#
# Date:    May, 1992
#
# Notes:   This package requires curseperl
#          (distributed with perl 4.19 in the usub directory).
#
#          Use:
#             &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");
#
#****************************************************************************

package perlmenu;

$did_initterm = 0;
$curses_application = 0;
$menu_exit_routine = "main'clear";

#**********
#  MENU_CURSES_APPLICATION
#
#  Function:	Indicate application is using curses calls.  If called, 
#		the menu routines will not do initscr and endwin calls 
#		(the application must do them).
#
#  Call format:	&menu_curses_application;
#
#  Arguments:	None
#
#  Returns:	Nothing
#**********
sub main'menu_curses_application {
  $curses_application = 1;
}

#**********
#  MENU_QUIT_ROUTINE
#
#  Function:	Specify a "cleanup" routine to be called before a "quit"
#		from the application is processed.
#
#  Call format:	&menu_quit_routine("string");
#
#  Arguments:	String containing name of exit routine to call.
#
#  Returns:	Nothing.
#
#**********
sub main'menu_quit_routine {
  $menu_exit_routine = "main'@_";
}

#**********
#  MENU_INIT
#
#  Function:	Initialize menu type (numbered or unnumbered), arrays and 
#		title.
#
#  Call format:	&menu_init([0|1],"Top Title");
#
#  Arguments:   Boolean flag indicating whether or not a arrows and numbers
#               are desired (0=no, 1=yes) and title text (for the top line
#               of menu).
#
#  Returns:	Nothing
#
#  Notes:	If the title string begins with a "-" the title is not
#		presented in reverse-video ("standout") representation.
#**********
sub main'menu_init {

#
#  Perform initscr if not a curses application
#
  if (!$curses_application) {
    &main'initscr;
  }

#
#  Load "magic sequence" array based on terminal type
#
  if (!$did_initterm) {		# Get terminal info (if we don't have it).
    &defbell unless defined &bell;

    $ku = &main'getcap('ku');	# Cursor-up
    $kd = &main'getcap('kd');	# Cursor-down
    $cr = &main'getcap('cr');	# Carriage-return
    $nl = &main'getcap('nl');	# New-line
    $ansi_ku = "\033[A";	# Ansi cursor-up (for DEC xterm)
    $ansi_kd = "\033[B";	# Ansi cursor-down (for DEC xterm)

    @magic_seq = ($ku,$ansi_ku,$kd,$ansi_kd,$cr,$nl,"\n",
		  "n","N","p","P","f","F"," ","b","B");
    $did_initterm = 1;
  }

  $menu_numbered = @_[0];
  $menu_top_title = @_[1];

# Check for title format character.
  $menu_top_title_attr = 0;
  if (substr($menu_top_title,0,1) eq '-') {
    $menu_top_title = substr($menu_top_title,1);
    $menu_top_title_attr = 1;
  }

# Center top title
  if (length($menu_top_title) > $main'COLS) {
    $menu_top_title = substr($menu_top_title,0,$main'COLS);
    $menu_top_title_col = 0;
  }
  else {
    $menu_top_title_col = int($main'COLS/2) - int(length($menu_top_title)/2);
  }

# Init selection array
  @menu_sel_text = ();		# Clear menu arrays
  @menu_sel_action = ();
  $menu_index = 0;		# Reset flags

  $first_line = 2;
  $last_line = $main'LINES - 3;
  $items_per_screen = $last_line - $first_line + 1;
}

#***********
#  MENU_ITEM
#
#  Function:	Add an item to the active menu.
#
#  Call format:	&menu_item("What you see","test_rtn");
#
#  Arguments:	String presented in menu, string returned if selected
#
#  Returns:	Number of items currently in the menu.
#***********
sub main'menu_item {
  local($item_text,$item_sel) = @_;
  local($sel_num,$sel_str);

# Prepend selection number (if a numbered menu)
  if ($menu_numbered) {
    $sel_num = $menu_index + 1;
    $sel_str = "  ";
    if ($sel_num < 1000) { $sel_str .= " "; }
    if ($sel_num < 100) { $sel_str .= " "; }
    if ($sel_num < 10) { $sel_str .= " "; }
    $sel_str .= "$sel_num) ";
    $item_text = $sel_str.$item_text;
  }

# Truncate lines that would wrap
  if (length($item_text) > $main'COLS - 1) {
    $item_text = substr($item_text,0,$main'COLS - 1);
  }

# Load into arrays and adjust index
  @menu_sel_text[$menu_index] = $item_text;
  @menu_sel_action[$menu_index] = $item_sel;
  $menu_index++;
}

#**********
#  MENU_DISPLAY 
#
#  Function:	Display items in menu_sel_text array, allow selection, and
#		return appropriate selection-string.
#
#  Call format:	$sel = &menu_display("Prompt text");
#
#  Arguments:   Prompt text (for the bottom line of menu)
#
#  Returns:     Select action string (from second param on &menu_init) OR
#		"%UP%" (if "u"|"U" pressed) OR
#               "%EMPTY% if nothing in menu to display
#
#  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.
#**********
sub main'menu_display {
  $total_items = $#menu_sel_text + 1;
  if ($total_items <= 0) {
    &main'nocbreak;		# ALWAYS turn off "cbreak" mode
    &main'echo;			# ALWAYS turn on "echo"
    return("%EMPTY%");
  }

  &main'cbreak;			# cbreak mode (each character available)
  &main'noecho;			# Menus are always "noecho"

  if ($total_items <= $items_per_screen) { $menu_single_page = 1; }
  else { $menu_single_page = 0; }

  $menu_prompt = @_[0];
  if ($menu_prompt eq "") {
    if ($menu_single_page) {
      $menu_prompt = "Move with up/down arrows  q=quit u=up-a-menu";
    }
    else {
      $menu_prompt = 
	"Move with up/down arrows, f=fwd-page b=back-page q=quit u=up-a-menu";
    }
  }
  if (length($menu_prompt) > $main'COLS - 7) {
    $menu_prompt = substr($menu_prompt,0,$main'COLS - 7);
  }

  $arrow_line = $first_line;
  $menu_top_item = 0;
#
# Clear screen and add top title and bottom prompt
#
  &menu_top_bot;
  $move_amt = 0;
  $number = 0;
   
  while (1) {
    $number_shown = $menu_top_item + $items_per_screen;
    if ($number_shown > $total_items) { $number_shown = $total_items; }
    $percent = int($number_shown * 100 /$total_items);

    &menu_page;			# Display current page
    &main'refresh;		# Update screen
#
# Collect key sequences until something we recoginize 
# (or we know we don't care)
#
    $collect = "";
    $action = "";
    $possible = $#magic_seq;	# Set number of possible matches 

seq_seek:
    while ($possible > 0) {
      $ch = &main'getch;

      if ($collect eq "") {	# Numbers/refresh allowed yet ...
	if (($ch eq "r") || ($ch eq "R")) {		# Refresh
	  &main'clear;
	  &menu_top_bot;
	  &menu_page;
	  &main'refresh;
	  next seq_seek;
	}
	if (($ch eq "\177") || ($ch eq "\010")) {	# Delete/BS num-reset
	  $number = 0;
	  $arrow_line = $first_line;
	  &menu_page;
	  &main'refresh;
	  next seq_seek;
	}
	$digit_val = index("0123456789",$ch);
	if ($digit_val >= 0) {				# It IS a number ...
	  $number = $number * 10 + $digit_val;
	  if ($number >= $menu_top_item + 1) { 
	    if ($number <= $menu_bot_item + 1) {
	      $arrow_line = $number - $menu_top_item + $first_line - 1;
	    } else {
	      &bell;
	      $number = 0;
	      $arrow_line = $first_line;
	    }
	    &menu_page;
	    &main'refresh;
	  }
	  next seq_seek;
	}
      }

      $collect = $collect.$ch;

      if (($collect eq "Q") || ($collect eq "q")) {
        &main'clear;
	&main'move(0,0);
	&main'addstr("Do you really want to quit? y");
	&main'move(0,28);
	&main'refresh;
	$ch = &main'getch;
	if (($ch eq $cr) || ($ch eq $nl) || ($ch eq "\n")) { $ch = "y"; }
	$ch =~ tr/A-Z/a-z/;
	if ($ch eq "y") {
	  if ($menu_exit_routine ne "") {
	    &$menu_exit_routine;
	  }
	  if (!$curses_application) { &main'endwin; }
	  &main'nocbreak;
	  &main'echo; 
	  &main'clear;
	  &main'refresh; 
	  exit(0);
	}
	&menu_top_bot;		# Re-display current page
	&menu_page;
	&main'refresh;
	$collect = "";
      }
      if (($collect eq "U") || ($collect eq "u")) {
	if (!$curses_application) { &main'endwin; }
	&main'nocbreak;
	&main'echo;
	&main'clear;
	&main'refresh;
	return("%UP%");
      }

      $i = 0;
      $possible = 0;
try:
      while ($i <= $#magic_seq) {
        if (length($collect) > length($magic_seq[$i])) {
	  $i++;
	  next try;
        }
	if (substr($magic_seq[$i],0,length($collect)) eq $collect) {
          $possible++;
	  if ($collect eq $magic_seq[$i]) {
            $action = $magic_seq[$i];
            last seq_seek;
          }
        }
        $i++;
      } # end while
    }
#
#  Perform action based on keystroke(s) received
#
    $move_amt = 0;
    if ($action ne "") {
      $last_arrow_line = $arrow_line;
      if (($action eq $kd) || ($action eq $ansi_kd)) {		# down-arrow
        if ($arrow_line < $max_sel_line) { $arrow_line++; }
        else {
	  if ($arrow_line == $last_line) { $move_amt = 1; }
        }
      }
      elsif (($action eq $ku) || ($action eq $ansi_ku)) {	# up-arrow
        if ($arrow_line > $min_sel_line) { $arrow_line--; }
        else { $move_amt = -1; }
      }
      elsif (($action eq "n") || ($action eq "N") ||     # next/forward
	     ($action eq "f") || ($action eq "F") || ($action eq " ")) {
	$move_amt = $items_per_screen;
      }
      elsif (($action eq "p") || ($action eq "P") ||	# previous/backward
	     ($action eq "b") || ($action eq "B")) {
	$move_amt = -$items_per_screen;
      }
      elsif (($action eq $cr) || ($action eq $nl) || 
	     ($action eq "\n")) {			# select
	if ($number) { $item = $number - 1; }
	else { $item = $menu_top_item + $arrow_line - $first_line; }
	if (($item < $menu_top_item) || ($item > $menu_bot_item)) {
	  &bell;
	  $number = 0;
	}
	else {
	  if (!$curses_application) { &main'endwin; }
	  &main'nocbreak;
	  &main'echo;
	  &main'clear;
	  &main'refresh;
	  return(@menu_sel_action[$item]);
	}
      }
#
# Check for paging of the menu text
#
      if ($move_amt != 0) {
	if ($move_amt < 0) { # Move backward
	  $menu_top_item = $menu_top_item + $move_amt;
	  if ($menu_top_item < 0) { $menu_top_item = 0; }
	}
	else { # Move forward
	  if ($menu_top_item + $move_amt < $total_items) {
	    $menu_top_item = $menu_top_item + $move_amt;
	  }
	} 
      }
#
# Erase the last selection arrow
#
      if ($menu_numbered) {
	&main'move($last_arrow_line,0);
	&main'addstr("  ");
      }
    }
  } # end until
}

#**********
#  MENU_TOP_BOT -- Display top and bottom lines of current menu
#**********
sub menu_top_bot {
  &main'clear;
  &main'move(0,$menu_top_title_col);
  if ($menu_top_title_attr == 0) { &main'standout; }
  &main'addstr($menu_top_title);
  if ($menu_top_title_attr == 0) { &main'standend; }

  &main'move($last_line+2,7);
  &main'addstr($menu_prompt);
}

#**********
#  MENU_PAGE -- Display one page of menu selection items.
#**********
sub menu_page {

# Update percentage on bottom line
  &main'move($last_line+2,0);
  &main'standout;
  if ($menu_single_page) { &main'addstr("(All) "); }
  else { &main'addstr(sprintf("\(%3d%%\)",$percent)); }
  &main'standend;
 
# Display current page of menu
  $item = $menu_top_item;
  $menu_bot_item = $menu_top_item;
  $curr_line = $first_line;
  $min_sel_line = $first_line;
  $max_sel_line = $first_line;
  while ($curr_line <= $last_line) {
    &main'move($curr_line,0);
    &main'clrtoeol;
    $sel_num = $item + 1;
    if ($item < $total_items) {
      &main'addstr("$menu_sel_text[$item]");
      $max_sel_line = $curr_line;
      $menu_bot_item = $item;
    }
    $item++;
    $curr_line++;
  }
 
#  Position the selection arrow on the screen (if numbered menu)
  if ($arrow_line > $max_sel_line) { $arrow_line = $max_sel_line; }
  &main'move($arrow_line,0);
  if ($menu_numbered) { &main'addstr("->"); }
}

sub defbell {
  eval q#
    sub bell { print "\007"; }
  #;
}

1;
\End\Of\Shar\
else
  echo "will not over write ./menu.pl"
fi
if `test ! -s ./ultpatch`
then
echo "writing ./ultpatch"
cat > ./ultpatch << '\End\Of\Shar\'
*** bsdcurses.mus.dist	Tue Jun  9 11:02:56 1992
--- bsdcurses.mus	Tue Jun  9 11:04:40 1992
***************
*** 54,60 ****
      US_erase,
      US_werase,
      US_flushok,
-     US_idlok,
      US_insch,
      US_winsch,
      US_insertln,
--- 54,59 ----
***************
*** 83,101 ****
      US_noraw,
      US_scanw,
      US_wscanw,
-     US_baudrate,
      US_delwin,
      US_endwin,
-     US_erasechar,
      US_getcap,
      US_getyx,
      US_inch,
      US_winch,
      US_initscr,
-     US_killchar,
      US_leaveok,
      US_longname,
-     US_fullname,
      US_mvwin,
      US_newwin,
      US_nl,
--- 82,96 ----
***************
*** 102,109 ****
      US_nonl,
      US_scrollok,
      US_subwin,
-     US_touchline,
-     US_touchoverlap,
      US_touchwin,
      US_unctrl,
      US_gettmode,
--- 97,102 ----
***************
*** 161,167 ****
      make_usub("erase",		US_erase,	usersub, filename);
      make_usub("werase",		US_werase,	usersub, filename);
      make_usub("flushok",	US_flushok,	usersub, filename);
-     make_usub("idlok",		US_idlok,	usersub, filename);
      make_usub("insch",		US_insch,	usersub, filename);
      make_usub("winsch",		US_winsch,	usersub, filename);
      make_usub("insertln",	US_insertln,	usersub, filename);
--- 154,159 ----
***************
*** 190,208 ****
      make_usub("noraw",		US_noraw,	usersub, filename);
      make_usub("scanw",		US_scanw,	usersub, filename);
      make_usub("wscanw",		US_wscanw,	usersub, filename);
-     make_usub("baudrate",	US_baudrate,	usersub, filename);
      make_usub("delwin",		US_delwin,	usersub, filename);
      make_usub("endwin",		US_endwin,	usersub, filename);
-     make_usub("erasechar",	US_erasechar,	usersub, filename);
      make_usub("getcap",		US_getcap,	usersub, filename);
      make_usub("getyx",		US_getyx,	usersub, filename);
      make_usub("inch",		US_inch,	usersub, filename);
      make_usub("winch",		US_winch,	usersub, filename);
      make_usub("initscr",	US_initscr,	usersub, filename);
-     make_usub("killchar",	US_killchar,	usersub, filename);
      make_usub("leaveok",	US_leaveok,	usersub, filename);
      make_usub("longname",	US_longname,	usersub, filename);
-     make_usub("fullname",	US_fullname,	usersub, filename);
      make_usub("mvwin",		US_mvwin,	usersub, filename);
      make_usub("newwin",		US_newwin,	usersub, filename);
      make_usub("nl",		US_nl,		usersub, filename);
--- 182,196 ----
***************
*** 209,216 ****
      make_usub("nonl",		US_nonl,	usersub, filename);
      make_usub("scrollok",	US_scrollok,	usersub, filename);
      make_usub("subwin",		US_subwin,	usersub, filename);
-     make_usub("touchline",	US_touchline,	usersub, filename);
-     make_usub("touchoverlap",	US_touchoverlap,usersub, filename);
      make_usub("touchwin",	US_touchwin,	usersub, filename);
      make_usub("unctrl",		US_unctrl,	usersub, filename);
      make_usub("gettmode",	US_gettmode,	usersub, filename);
--- 197,202 ----
***************
*** 312,322 ****
  I	bool		boolf
  END
  
- CASE int idlok
- I	WINDOW*		win
- I	bool		boolf
- END
- 
  CASE int insch
  I	char		c
  END
--- 298,303 ----
***************
*** 466,474 ****
  CASE int noraw
  END
  
- CASE int baudrate
- END
- 
  CASE int delwin
  I	WINDOW*		win
  END
--- 447,452 ----
***************
*** 476,484 ****
  CASE int endwin
  END
  
- CASE int erasechar
- END
- 
      case US_getcap:
  	if (items != 1)
  	    fatal("Usage: &getcap($str)");
--- 454,459 ----
***************
*** 522,530 ****
  CASE WINDOW* initscr
  END
  
- CASE int killchar
- END
- 
  CASE int leaveok
  I	WINDOW*		win
  I	bool		boolf
--- 497,502 ----
***************
*** 535,545 ****
  IO	char*		name
  END
  
- CASE int fullname
- I	char*		termbuf
- IO	char*		name
- END
- 
  CASE int mvwin
  I	WINDOW*		win
  I	int		y
--- 507,512 ----
***************
*** 570,587 ****
  I	int		cols
  I	int		begin_y
  I	int		begin_x
- END
- 
- CASE int touchline
- I	WINDOW*		win
- I	int		y
- I	int		startx
- I	int		endx
- END
- 
- CASE int touchoverlap
- I	WINDOW*		win1
- I	WINDOW*		win2
  END
  
  CASE int touchwin
--- 537,542 ----
\End\Of\Shar\
else
  echo "will not over write ./ultpatch"
fi
echo "Finished archive 1 of 1"
exit
-- 
Steven L. Kunz
Networked Applications | Usenet News Admin.
Iowa State University Computation Center, Iowa State University, Ames  IA
INET: skunz@iastate.edu     BITNET: gr.slk@isumvs.bitnet