Cmenu
view release on metacpan or search on metacpan
#****************************************************************************
# Cmenu.pm -- Perl Curses Menu Support Facility
#
# Last updated Time-stamp: <01/10/20 23:14:23 devel>
#
#
# Author: Andy Ferguson (cmenu@afccommercial.co.uk)
# AFC Commercial
# Bangor, Northern Ireland
# BT19 1PF
#
# 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
# Version 1.1 -- Oct, 2001 -- Improvements in rc configuration
#
# 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.
#
# $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_attributes=(); # load and hold color definitions
my $menu_hascolor; # terminal colour capability flag
# 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
# User hacks
my($menu_hack25)=0; # hack to make a small screen bigger
# 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);
$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
$menu_hascolor=eval {has_colors()};
start_color();
# ##################################################################################
# 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
# LEFT : move left
# LYNXR : move right - can mimic lynx-style motion
# LYNXL : 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
@y=split(/\|/,$x);
for($i=0;$i<=$#y;$i++) {
ATTRIBUTES: for($y[$i]) {
/NORMAL/ && do {
$atts=$atts|A_NORMAL;
last ATTRIBUTES;
};
/STANDOUT/ && do {
$atts=$atts|A_STANDOUT;
last ATTRIBUTES;
};
/UNDERLINE/ && do {
$atts=$atts|A_UNDERLINE;
last ATTRIBUTES;
};
/REVERSE/ && do {
$atts=$atts|A_REVERSE;
last ATTRIBUTES;
};
/BLINK/ && do {
$atts=$atts|A_BLINK;
last ATTRIBUTES;
};
/DIM/ && do {
$atts=$atts|A_DIM;
last ATTRIBUTES;
};
/BOLD/ && do {
$atts=$atts|A_BOLD;
last ATTRIBUTES;
};
}
}
}
return($atts);
}
#**********
# 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_attributes{"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_attributes{"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,$menu_attributes{"shadow"});
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_attributes{"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)=@_;
my($key);
&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;
}
#***********
# 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_attributes{"scroll"});
addstr($menu_window, "-^-");
} else {
attrset($menu_window,$menu_attributes{"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_attributes{"scroll"});
addstr($menu_window, "-v-");
} else {
attrset($menu_window,$menu_attributes{"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
/LEFT/ && do { # Left arrow
# Treat this like an UP-Menu request
$action="UP";
# redo KEYWAIT;
};
/RITE/ && do { # Right arrow
# Treat this like a RETURN
$action="DOWN";
# redo KEYWAIT;
};
/LYNXL/ && do { # Left arrow
# Treat this like an UP-Menu request
$action="QUIT";
redo KEYWAIT;
};
/LYNXR/ && do { # Right arrow
# Treat this like a RETURN
$action="RET";
redo KEYWAIT;
};
/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;
};
# 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("");
}
$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_attributes{"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_attributes{"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_attributes{"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_attributes{"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;
if($menu_hack25) {
$menu_pane_lines=$menu_inlay_lines-3-$menu_sub_title_lines;
} else {
$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;
#
# 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 $Cmenu::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_attributes{"title"});
addstr($menu_pane,$menu_sel_label[$m_item]);
# Highlight first letter
move($menu_pane,$i,$m_indent);
attrset($menu_pane,$menu_attributes{"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_attributes{"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_attributes{"option"});
addch($menu_pane,"[");
if($menu_sel_flag[$m_item]>0) {
attrset($menu_pane,$menu_attributes{"title"});
addch($menu_pane,"X");
attrset($menu_pane,$menu_attributes{"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_attributes{"option"});
addch($menu_pane,"(");
if($menu_sel_flag[$m_item]>0) {
/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 {
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 2 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_on_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 ($selection[0]) is usually the key pressed to close the
menu was closed; this will rarely be a valid menu item - check it to make sure
an "abort" was not requested.
=head2 menu_button_set
Each menu has up to 3 buttons which can be activated. 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" although your
scripts can interret these responses however you wish). The <TAB> key
traverses the buttons bar.
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 pop-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
Remember to close the popup or the menu display will get confused.
=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.
( run in 2.434 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )