From: [email-masked] (Dale R. Worley)
Date: Tue, 14 Apr 1992 16:53:54 GMT
Newsgroups: comp.lang.perl

Forms system
				version 1.0
				  Design

1. Form

A "form" consists of a sequence of "fields".  Each field contains the
information necessary to display and input one value.

2. Input commands

At any time, form input is either editing the value of a particular field
("in a field"), or it is transitting between fields ("out of a field").
Entry into a field is done only when a command is executed which would change
the value of the field; bringing the cursor to rest on a particular field is
not sufficient to enter the field.  Conversely, exiting a field is done only
when some command is executed which would transit to another field, or exit
the form entirely.  However, exiting a field causes its value to be checked 
for validity, and if the validity check fails, the command that caused exiting
of the field is not performed.

The commands which are executed out of a field are:

	LFD	accept contents of form
		(also C-j)
	C-c	abort the form
		(exits the current field, if any, with C-y, and thus never
		fails)
	RET	go to next field
	TAB	go to previous field

The commands which are executed in a field are:

	C-y	restore previous value of the field and exit from field
		(bypasses validity check on previous value)
	C-v	perform validity check on field
	C-v C-v exit from field, if it is valid

	C-u	clear field
	C-k	clear to end of field
	C-r	clear to beginning of field

	C-a	go to beginning of field
	C-e	go to end of field

	C-b	go back one character
	C-f	go forward one character

	C-d	delete next character
	DEL	delete previous character
		(also C-h)

The help commands are:

	C-p	give help on current field
	C-p C-p	show help screen describing all commands

The help command (C-p) gives help on the field the cursor is in
(whether or not it has been entered) by displaying its help_message
attribute value in the field named MSG.  However, if there is no such
attribute or no field named MSG, or if there is no current field
(because no visible field is writeable), it behaves like C-p C-p.

In addition, all printing characters (SPC through ~) are commands to insert
the character into the field value.  Note, however, that the exact meaning
of the in-field commands is determined by the field definition.

The function keys can also be used as commands.  See the section
titled "function keys".

3.  Attributes

Each field is described by a set of "attributes".  A form is stored in
an associative array, and the attributes of the fields are stored in
elements of the associative array: The value of the attribute $attr of
field $field in form $form is stored in $form{$field,$attr}.  Certain
attributes are used by the forms system as a whole, and are stored with
the null string as field name. 

The forms system reserves the package name "forms" for its data and
subroutines.  In addition, the package "forms_user" is used for storing
code and data used by user-written routines.  Names of the form
"forms_generated_0000" are generated by the forms system, and so should
be avoided, both in the two packages and as field names. 

4.  Values

A value is represented in three distinct ways.  While this may seem
excessively complicated, it appears to be the only general framework
that handles the myriad possible ways of displaying and inputting field
values in a reasonably extensible way.  The forms of a value are:

Attribute "value": The actual value of the field, considered the output
of the form when it is successfully executed, and the input, when the
fields are pre-loaded before the form is executed.  This value can be
any Perl value that can be stored in a scalar. 

Attribute "displayed_value": The field, interpreted as a set of
characters.  For example, if the value is to be a number, the displayed
value may be a character representation of the number.  In addition, if,
say, -1 is a special value of the number, the displayed value may code
it as a particular character string, say, "Unknown". 

Attribute "displayed_field": The field, as displayed on the screen. 
This attribute is always exactly as long as the field length (attribute
"field"length"), and is what is displayed on the screen.

The translation between these forms is as follows:

The translation value -> displayed_value is done by the function stored
in attribute construct_displayed_value.  It is called with two
arguments, the field name and the value, and returns the displayed
value.

The translation displayed_value -> displayed_field is done by the
function stored in attribute initialize_displayed_field.  It is called
with two arguments, the field name and the displayed value, and
returns the displayed_field.  In addition, it sets the variable
$forms'cursor_location to the correct initial location of the cursor.
(Cursor locations are relative to the beginning of the field, which is
location 0.)  This function is always called upon entry to a field,
thus it may set additional attributes for the field for the user of
the editing functions for the field.

The displayed value and displayed field are both edited by the
function stored in attributes "edit" and "insert".  They are called
with two arguments, the field name and the character typed.  The
character is either a printing character (in which case "insert" is
called) or a non-printing character that is not one of the out-field
input command characters listed above (in which case "edit" is
called).  The functions are responsible for updating the displayed
value, the displayed field, and $forms'cursor_location appropriately,
and must also report erroneous input characters.

When the field is exited, two further attribute functions are called:
The attribute "validate_displayed_value" is called with two arguments,
the field name and the displayed value.  It returns true if the value of
the field is considered valid, and false if it is not.  If it returns
false, exit from the field is blocked.  If it returns true, the function
stored in attribute "interpret_displayed_value" is called with the field
name and the displayed value as arguments.  It returns the value.  Since
interpret_displayed_value is always called immediately after a
successful call to validate_displayed_value, the latter may set global
variables for the former.  (This is the only circumstance where user
functions can safely set its own global variables.)

5.  Formatting attributes

Attribute "label": The text of the label to be applied to the field.

Attribute "label_location": Two numbers, seperated by commas, giving the
location of the first character of the label.  (Locations are always given
in the order "row,column", both of which are 0-origin, with the 0 row being
the top row and the 0 column the leftmost column.)

Attribute "field_length": The number of characters to be allocated to
the field.  If this attribute is undefined or 0, the field has no
input area, and only its label is relevant.

Attribute "field_location": The location of the field.

Attribute "read_only": If true, the field may not be entered in.  The
RET and TAB commands skip over the field. 

Attribute "invisible": If true, the field and its label are not displayed
and cannot be altered.

Attribute "initially_invisible":  Set from attribute "invisible" when
the form array is constructed, and used to restore attribute "invisible".

Attribute "canonicalize": If true, when the field is exited, the value
that was created is used to re-calculate the displayed value and the
displayed field. 

Attribute "help_message": Displayed by the C-p command when the cursor
is in the field.

In addition, there are several attributes that apply to the form as a whole:

Attribute "fields": A comma-separated list of the field names, in
their natural order.  A field's position in the natural order is
determined by the location in which it was first given an attribute.
RET cycles through the fields in the natural order, and TAB cycles
through them in the reverse of the natural order.

Attribute "initialize": A function to be executed once the form is set up
but before any forms processing is performed.

Attribute "finalize": A function to be executed when the form is accepted
with LFD.  If it returns false, the accept is rejected.

Attribute "forms_version": This attribute declares that the form
structure conforms to a particular version of the forms package, and
all upward-compatible extensions of it.  If provided, the forms system
checks that it is capable of processing the given version of the forms
structure.  This document describes version 1.0.

The field MSG is used to display help messages for fields (via the C-p
command and the help_message attribute) and other messages (via the
report_error and report_message functions).  If it is not present,
these messages are not displayed.

6.  Manipulating other fields

The edit_field function or other functions of a field may need to alter
the value or visibility of a field.  If it does so, it should call the
appropriate function:

$forms'changed_value($field) is used to signal that the value attribute of
the given field has been changed, and the displayed value and displayed
field should be updated.

$forms'changed_visibility($field) is used to signal that the invisible
attribute of the given field has been changed, and the screen should
be updated appropriately.  (It should not be called if the invisible
attribute has not been changed, unless the user makes sure that no
overlapping fields are visible.)  The user is responsible for making
sure that the fields that are visible at a given moment do not
overlap.  If a field that is being made invisible and a field that is
being made visible overlap, the user should be sure to make the one
field invisible before making the other field visible.

7.  Input representation

A form can be represented as a sequence of lines of text, or more
exactly, as an array of strings.  (Ending linefeeds on the strings are
ignored.) The representation gives the values of the attributes in a
straightforward way:

	field.attribute = value

Whitespace around the field and attribute names and the "=" are ignored; 
leading and trailing whitespace around the value is also ignored.
Lines that are entirely whitespace, or whose first non-whitespace character
is "#" are ignored.

Attributes of the form as a whole are set in the expected manner:

	.attribute = value

A line of the form

	field:

is used to provide a default field name for succeeding attributes.  The
default field name is invoked by omitting the "field." part of the
attribute setting line:

	user_name:
	    field_location = 2,5
	    field_length = 8

A line of the form

	:

provides a generated field name as the default field name.  It is most often
used for fields that have labels but no value.

Values are usually interpreted as character strings.  However, certain
values are interpreted differently:

	&name

This value is interpreted as "*forms_user'name", which is the correct
way to represent functions as attributes. 

	&package'name

This value is interpreted as "*package'name".

	@name

This value is interpreted as "*forms'name".  (More exactly, the three
above forms are evaluated directly, after replacing the first
character with "*".  If the first character is "&", it is evaluated in
package forms_user; if it is "@", it is evaluated in package forms.
Thus, "&" is used for functions defined by the user, and "@" is used
for functions supplied by forms.

	* expression

This value is interpreted by evaluating "expression", in the package
forms_user.

	"text"

This value is interpreted as "text", allowing characters that would
otherwise be interpreted specially.

	field.attribute = {
		text
		};

This value is used to construct a subroutine in the forms_user package.
The value itself is interpreted as "*forms_user'<subroutine_name>".
The text of the subroutine is terminated by a line consisting of "};",
possibly with leading and trailing whitespace.

Interspersed with the attribute values can be sets of lines with the format

	sub name {
		text
		};

These forms are used to define subroutines.  They are evaluated in the
forms_user package. 

In addition, there can be lines with the format

	* expression

The expression is evaluated in the package forms_user.

Note that "* expression" constructions and subroutines defined in the
forms description are evaluated in the order that they are in the
forms description, at the time that the representation is processed by
&forms'process_representation.

The input representation of a form is transformed into an array with
the function:

	&forms'process_representation(*array, @representation)

where the form attributes will be stored in the associative array %array, 
and the representation is taken from @representation.  The function returns
true if no error is found and false if an error was found.  The error message
is stored in $forms'error.

The function can be used to process input representations from files:

	&forms'process_representation(*array, <FILE>);

and from here-documents:

	&forms'process_representation(*array, split(/\n/, <<'EOF'))
	text...
	EOF

If the input representation is in a file, a more convenient form can be used:

	&forms'process_representation_from_file(*array, $file_name, $inc)

This function reads the representation from the given file and processes it.
In addition, if $inc is true, @INC is searched for $file_name, just as require
does.  If the file does not exist, the function aborts execution.

These functions set certain attributes in addition to those that are mentioned
in the input representation:  "initially_invisible" is set from "invisible",
and "fields" is set to be the list of fields in the form.

8.  Processing forms

Several functions are used to process forms:

	&forms'clear_values(*array)

This function clears (sets to undef) all of the value attributes of the given
form.  It is used to clear a form in preparation for further input.

	&forms'clear_values_and_redisplay

This function is used during forms input to clear all of the value
attributes and force redisplay of all the fields (via
&forms'changed_value).

	&forms'reset_visibility(*array)

This function sets the invisible attributes from the initially_invisible
attributes. 

	&forms'process_form(*array)

This form displays the given form and gets the user's input.  Initially
displayed values are obtained from the value attributes of the form, and
the returned values are returned in the value attributes.  The function
returns true if the form as accepted and false if it was aborted.  During
execution of the form, %forms'form is aliased to %array.  &forms'process_form
is not intended to be executed recursively.

	&forms'dump_form(*array, filehandle)

Prints out all of the attributes in a form.  If filehandle is omitted,
STDOUT is used.

9.  Service routines

	&forms'report_message(text)

is used to display a message in the field named MSG (if there is one).

	&forms'report_error(text)

is used to display an error message in the MSG field.  (It performs a
report_message and then rings the bell.)

10.  Function keys

The function keys can be used as commands if they are defined by a
form.  Function keys are typed as:

	ESC
	single optional 'O' or '['
	zero or more characters in the range '!' to '?'
	one character '@' or greater

(This should probably be changed to a more flexible system.)  They are
translated into function key identifications (F1 through F10) by the
table %forms'function_key.  The function key identifications for F1 to
F9 are loaded from the 'k1' through 'k9' capabilities from the termcap
entry, and the F10 identification is taken from the 'k;' capability
(or if it is not present, the 'k0' capability).  In addition, several
standard function key codes are loaded:

	Key     Sun     Sun     VT100   Manual typing	Termcap

	F1      \e[224z \e[11~  \eOP    \e1f    \e1F	k1
	F2      \e[225z \e[12~  \eOQ    \e2f    \e2F 	k2
	F3      \e[226z \e[13~  \eOR    \e3f    \e3F 	k3
	F4      \e[227z \e[14~  \eOS    \e4f    \e4F 	k4
	F5      \e[228z \e[15~          \e5f    \e5F 	k5
	F6      \e[229z \e[17~          \e6f    \e6F 	k6
	F7      \e[230z \e[18~          \e7f    \e7F 	k7
	F8      \e[231z \e[19~          \e8f    \e8F 	k8
	F9      \e[232z \e[20~          \e9f    \e9F 	k9
	F10     \e[-1z  \e[21~          \e0f    \e0F 	k; or k0
					\e10f   \e10F

A function key is considered "local" if the cursor is sitting in a
field and that field has an attribute "F1" (or whatever).  A function
key is considered "global" if it is not local and there is a global
attribute "F1" (or whatever).  Escape sequences that are neither local
nor global produce an error message.  If a local function key is
given, the field is enterd (if it has not been) and the function named
by the attribute is executed.  If a global function key is given, the
field is exited (if it has not been) and the function named by the
attribute is executed.

11. Problem with curseperl

Curseperl has an ugly problem.  The following program does not do what
is expected (clear the screen) when $ARGV[0] is true:

    $ENV{'TERM'} = 'vt100';
    &initscr;
    if ($ARGV[0]) {
	    &getcap('k1');
	    &getcap('k2');
	    &getcap('k3');
	    &getcap('k4');
    }

    &clear;
    &refresh;
    &getch;

(This is using BSD curseperl on a Sun4 with SunOS 4.1.1)

getcap is just a wrapper for termcap's tgetstr, *using a fixed
internal buffer to collect the results from tgetstr*.  It appears that
this internal buffer has a finite size, and each successive return
value is written after the previous one, so if you do a few too many
getcap's, you write off the end of the buffer...

A solution which appears to work is to change the interface for
&getcap to call tgetstr directly, but to supply a new buffer each
time.  The patch for this is follows.  It is necessary to install this
patch, because forms.pl uses getcap() when it is loaded.  If this
patch can't be installed, you have to comment out the calls to
&'getcap in subroutine load_function_keys.  This may disable the
function keys defined in the termcap entry for the user's terminal.

*** bsdcurses.mus.old	Thu Apr  9 16:40:41 1992
--- bsdcurses.mus	Thu Apr  9 16:41:18 1992
***************
*** 476,484 ****
  CASE int erasechar
  END
  
! CASE char* getcap
! I	char*		str
! END
  
      case US_getyx:
  	if (items != 3)
--- 476,493 ----
  CASE int erasechar
  END
  
!     case US_getcap:
! 	if (items != 1)
! 	    fatal("Usage: &getcap($str)");
! 	else {
! 	    char* retval;
! 	    char*	str =		(char*)		str_get(st[1]);
! 	    char output[50], *outputp = output;
! 
! 	    retval = tgetstr(str, &outputp);
! 	    str_set(st[0], (char*) retval);
! 	}
! 	return sp;
  
      case US_getyx:
  	if (items != 3)

12. Standard field types

The forms system contains routines for inputting various standard
field types.

- Ordinary text fields

Ordinary text fields can be implemented with the attributes:

	construct_displayed_value = @char_field
	initialize_displayed_field = @id_cursor_after
	validate_displayed_value = @true
	interpret_displayed_value = @trim_trailing_space
	insert = @text_insert
	edit = @text_edit

When the field is entered, the cursor is placed after the first
non-blank character of the value.  When the field is exited, the
contents of the field have trailing spaces removed, and the remainder
is the field vbalue.

- Hidden text fields

Hidden text fields are just like ordinary text fields, except that the
field value is not displayed -- the characters of the value (up to the
last non-blank character) are displayed as periods (".").  Hidden text
fields are good for inputting passwords and similar things.  They can
be implemented with the attributes:

	construct_displayed_value = @char_field
	initialize_displayed_field = @id_cursor_after_hidden
	validate_displayed_value = @true
	interpret_displayed_value = @trim_trailing_space
	insert = @text_insert_hidden
	edit = @text_edit_hidden

- Enumerated fields

Enumerated fields allow one of a set of values to be selected.  Each
value is represented by one of a set of "representations", which are
what is displayed to the user and what the user types in the field to
specify a value.  The set of permissible values and their
representations are specified by the attribute "translate_table",
which is a list of items of the form "representation=value", separated
by backslashes ("\").  The representations and values are searched for
in translate_table, so they may not contain backslashes or equal
signs.  In addition, representations are recognized
case-insensitively, while values are represented case-sensitively.
When a value is translated to a representation, the first
representation listed is chosen.  A value that does not have a
representation should not be present.  A representation that does not
have a value will be rejected as invalid.  An example of an enumerated
field with the translation table

	Value	Representation
	1	Yes	(preferred representation)
	1	Y
	0	No	(preferred representation)
	0	N

is constructed by:

	construct_displayed_value = @enum_field
	initialize_displayed_field = @id_cursor_after
	validate_displayed_value = @enum_validate
	interpret_displayed_value = @enum_interpret
	translate_table = Yes=1\Y=1\No=0\N=0\=0
	insert = @text_insert
	edit = @text_edit
	canonicalize = 1

In this case, "canonicalize" is set, so that upon field exit the
value is displayed in the preferred representation.