A form is a collection of one or more pages of fields. The fields may be used for titles, labels to guide the user, or for data entry. The following example displays a simple form with five fields including two for data entry.
Sample Form Display
Sample Form Field 1: ___________ Field 2: ___________
To use the form routines, you specify
#include <form.h>
in your C program files and compile and link with the command line
cc [ flags ] files -lform -lcurses[ libraries ]
If you want to use the menu or panel routines as well, place the appropriate -l option before the option -lcurses.
This section introduces the basic ETI form terminology, lists the steps in a typical form application, and reviews the sample program that produced the output of the previous sample form display.
The following terms are helpful in working with ETI form functions:
In general, a form application program will
The following example shows the ETI program necessary for producing a form.
Code to Produce a Simple Form
#include <form.h> #include <string.h> FIELD * make_label (frow, fcol, label) int frow; /* first row*/ int fcol; /* first column*/ char * label; /* label*/ { FIELD * f = new_field (1, strlen (label), frow, fcol, 0, 0); if (f) { set_field_buffer (f, 0, label); set_field_opts (f, field_opts(f) & ~O_ACTIVE); } return f; } FIELD * make_field (frow, fcol, cols) int frow; /* first row*/ int fcol; /* first column*/ int cols; /* number of columns*/ { FIELD * f = new_field (1, cols, frow, fcol, 0, 0); if (f) set_field_back (f, A_UNDERLINE); return f; } main () { FORM * form; FIELD * f[6]; int i = 0; /* ETI initialization */ initscr (); nonl (); raw (); noecho (); wclear (stdscr); /* create fields */ f[0] = make_label (0, 7, "Sample Form"); f[1] = make_label (2, 0, "Field 1:"); f[2] = make_field (2, 9, 16); f[3] = make_label (3, 0, "Field 2:"); f[4] = make_field (3, 9, 16); f[5] = (FIELD *) 0; /* create and display form */ form = new_form (f); post_form (form); wrefresh (stdscr); sleep (5); /* erase form and free both form and fields */ unpost_form (form); wrefresh (stdscr); free_form (form); while (f[i]) free_field (f[i++]); /* ETI termination */ endwin (); exit (0); }
In this example, all text within the form is associated with a field. Fields may be active or inactive: active fields are affected by form processing, inactive fields are not. The underlined fields are active, whereas the label fields Sample Form, Field 1:, and Field 2: are inactive.
Turn now to the program itself. This example starts with two #include files. Every form program must include the header file form.h, which contains important definitions of form objects. This particular program uses the C string library function strlen, so it includes the header file string.h,whose definitions the string library function needs. See string(3C) in for details.
Next, there are two programmer-defined functions make_label and make_field, which we will discuss in a moment. Consider procedure main. It declares three objects:
The first five functions initialize low-level ETI (curses) for high-level ETI form functions. Function initscr initializes the screen, nonl ensures that a carriage return on using wgetch will not automatically generate a newline, raw passes input characters uninterpreted to your program,noecho disables echoing of your user's input (the form functions provide echoing where appropriate), and wclear(stdscr) clears the standard screen.
The statements that create the form's fields and labels in this example make calls to the programmer-defined functions make_label and make_field. You can do without these programmer-defined functions, but you may find them convenient. Both of them use the ETI function new_field. They take three arguments, which correspond to three of the six arguments of new_field.
The first argument of new_field is the number of rows of the field. In this example, it is always one. The last two arguments are often 0 as they are here; they will be explained in the next section. The second argument of new_field is the number of columns in the field. This number is determined from the third parameter in main's calls to make_label and make_field. For the label fields, the calls to make_label pass the string that is to constitute the field so that strlen can be used to count the length or number of columns of the string. For the fields to be edited by the end-user (had this example permitted entering data into the fields), calls to make_field simply pass the number of columns directly.
The third and fourth arguments to new_field correspond to the first and second arguments to make_label and make_field. They are the starting position(firstrow, firstcol) of the label or field in the form subwindow. (In this example, the default subwindow stdscr is used.) The last assignment to f[5]terminates the array with the NULL field pointer.
Once the function make_label creates the field for the label, it places the label in the field using function set_field_buffer. The second argument to this function is0 because the value of a field is stored in buffer 0. Finally, function make_label calls set_field_opts, which turns off the O_ACTIVE option for the field. This means that the field is ignored during form driver processing.
On the other hand, once the function make_field creates the field proper, it sets the field's background attribute to A_UNDERLINE. This has the effect of underlining the field so that it is visible.
After you create the fields for a form, you create the form itself using new_form. This function takes the pointer to the array of field pointers and connects the fields to the form. The pointer returned is stored in variable form - it will be passed to subsequent form manipulation routines. To display the form, function post_form posts it on the default subwindow stdscr, while wrefresh(stdscr) actually displays this subwindow on the terminal screen. The display remains for 5 seconds, as determined by sleep.
At this point, most forms would accept and process user input. To illustrate a very simple form, this program does not accept user input.
To erase the form, you first unpost it using unpost_form. This erases it from the form subwindow. The call to wrefresh actually erases the form from the display screen.Function free_form disconnects the form from its array of field pointers f.
The while loop, starting with the first field in the field pointer array, frees each field referenced in the array. The effect is to deallocate the space for each field.
We have met the last two lines of the program before. Function endwin terminates low-level ETI, while exit(0)terminates the program.
There are many ETI form routines not listed in the previous screen. These routines enable you to tailor your form programs to suit local needs and preferences. The following sections explain how to use all ETI form routines. Each routine is illustrated with one or more code fragments. Many of these are drawn from two larger form application programs listed at the end of the chapter. By reviewing the code fragments, you will come to understand the larger programs.
To create a form, you must first create its fields. The following functions enable you to create fields and later free them.
SYNOPSIS FIELD * new_field (rows, cols, firstrow, firstcol, nrow, nbuf) int rows, cols, firstrow, firstcol, nrow, nbuf; FIELD * dup_field (field, firstrow, firstcol) FIELD * field; int firstrow, firstcol; FIELD * link_field (field, firstrow, firstcol) FIELD * field; int firstrow, firstcol; int free_field (field) FIELD * field;
Unlike menu items which always occupy one row, the fields on a form may contain one or more rows. Function new_field creates and initializes a new field that is rows by cols large and starts at point (firstrow, firstcol) relative to the origin of the form subwindow. All current system defaults are assigned to the new field when it is created using new_field.
Variable nrow is the number of offscreen rows allocated for this field. Offscreen rows enable your program to display only part of a field at a given moment and let the user scroll through the rest. A zero value means that the entire field is always displayed, while a nonzero value means that the field is scrollable. A field can be created with nrow set to zero and allowed to grow and scroll if the field is made dynamic. See the section "Dynamically Growable Fields" for more detail.
Variable nbuf is the number of additional buffers allocated for this field. You can use it to support default field values, undo operations, or other similar operations requiring one or more auxiliary field buffers.
Variables rows and cols must be greater than zero, while firstrow, firstcol, nrow, and nbuf must be greater than or equal to zero.
Each field buffer is ((rows + nrow) * cols + 1) characters large. (The extra character position holds the NULL terminator.) All fields have one buffer (namely, field buffer 0) that maintains the field's value. This buffer reflects any changes your end-user may make to the field. See the section "Setting and Reading Field Buffers" for more details.
To create a form field occupation one row high and 32 columns wide, starting at position 2,15 in the form subwindow, with no offscreen rows and no additional buffers, you can write:
FIELD * occupation; occupation = new_field (1, 32, 2, 15, 0, 0); /* create field */
Generally you create all the fields for a form at the same point in your program.
The function dup_field duplicates an existing field at the new location firstrow, firstcol. During initialization, dup_field copies nearly all the attributes of its field argument as well as its size and buffering information. However, certain attributes, such as being the first field on a page or having the field status set, are not duplicated in the newly created field. See the sections "Creating and Freeing Forms" and "Manipulating Field Options" for details on these attributes.
Like dup_field, function link_field duplicates an existing field at a new location on the same form or another one. Unlike dup_field, however, link_field arranges that the two fields share the space allocated for the field buffers. All changes to the buffers of one field appear also in the buffers of the other. Besides enabling your user to enter data into two or more fields at once, this function is useful for propagating field values to later pages where only the first field is active (currently open to form processing). In this case, the inactive fields in effect become dynamic labels. See the section "Manipulating Field Options."
NOTE: Linked fields share only the space allocated for the field buffers-the attribute values of either field may be changed without affecting the other.
Consider field occupation in the previous example. To duplicate it at location 3,15 and link it at location 4,15, you write:
FIELD * dup_occ, * link_occ; dup_occ = dup_field (occupation, 3, 15); link_occ = link_field (occupation, 4, 15);
Functions new_field, dup_field, and link_field return a NULL pointer, if there is no available memory for the FIELD structure or if they detect an invalid parameter.
Function free_field frees all space allocated for the given field. Its argument is a pointer previously obtained from new_field, dup_field, or link_field.
NOTE: To free a field, be sure that the field is not connected to a form.
As described in the section "Creating and Freeing Fields" below, you can disconnect fields from forms by using functions free_form or set_form_fields.
To free a form and all its fields, you write:
FORM * form; /* get pointer to form's field pointer array using form_fields described in section below, "Changing and Fetching the Fields on an Existing Form" */ FIELD ** f = form_fields (form); free_form (form); /* free form */ while (*f) free_field (*f++); /* free each field and increment pointer */
Notice that you free the form before its fields.
If successful, function free_field returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL fieldpointer E_CONNECTED - connected field
Remember that the field pointer returned by new_field, dup_field, or link_field is passed to all field routines that record or examine the field's attributes. As with menu items, once a form field is freed, it must not be used again. Because the freed field pointer does not point to a genuine field, undefined results occur.
Recall that an attribute is any feature whose value can be set or read by an appropriate ETI function. A field attribute is a feature of a field whose value can be set or read by an appropriate ETI function. Field attributes include the field size and location.
This function enables you to determine the defining characteristics of a field - its size, position, number of offscreen rows, and number of associated buffers.
SYNOPSIS int field_info (field, rows, cols, firstrow, firstcol, nrow, nbuf) FIELD * field; int * rows, * cols, * firstrow, * firstcol, * nrow, * nbuf;
Because function field_info must return more than a single value and C passes arguments to functions ``by value'' only, field_info uses the pointer arguments rows, cols, firstrow,firstcol, nrow, and nbuf. These arguments are pointers to the locations used to return the requested information: the number of rows and columns comprising the field, the field starting location relative to the origin of its form subwindow, the number of offscreen rows, and the number of additional buffers.
As an example, consider how you might use field_info to determine a field's buffer size. You fetch the field's number of onscreen and offscreen rows and number of columns, and do the arithmetic, thus:
int bufsize (f) FIELD * f; { int rows, cols, firstrow, firstcol, offrow, nbuf; field_info (f, &rows, &cols, &firstrow, &firstcol, &offrow, &nbuf); /* add up size of field and its terminator */ return (rows + offrow) * cols + 1; }
Note the use of the address operator & to pass field_info the requisite pointers to the locations used to return the requested field information.
If successful, function field_info returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL field pointer
A dynamically growable field within a form will allow a user to add more data to a field than was specified when the field was originally created. Recall, when a field is created, a buffer is allocated based on the size of the field. With dynamically growable fields, if a user enters more data than the original buffer can hold, the buffer will grow as the user enters more data into the field. The application developer can specify the maximum growth of a field or allow a field to grow without bound.
A field can be made dynamically growable by turning off the O_STATIC field option. See the section "Manipulating Field Options'' for more information on changing field options.
Recall the library routine new_field; a new field created with rows set to one and nrow set to zero will be defined to be a one line field. A new field created with rows + nrow greater than one will be defined to be a multiline field.
A one line field with O_STATIC turned off will contain a single fixed row, but the number of columns can increase if the user enters more data than the initial field will hold. The number of columns displayed will remain fixed and the additional data will scroll horizontally.
A multiline field with O_STATIC turned off will contain a fixed number of columns, but the number of rows can increase if the user enters more data than the initial field will hold. The number of rows displayed will remain fixed and the additional data will scroll vertically.
It may be desirable to allow a field to grow, but within bounds. The following function can be used to limit the growth of a dynamic field either horizontally or vertically.
SYNOPSIS int set_max_field(field, max_growth) FIELD *field; int max_growth;
If field is a horizontally growable one line field, its growth will be limited to max_growth columns. If field is a vertically growable field, its growth will belimited to max_growth rows. To remove any growth limit, call set_max_field with max_growth set to zero. To query the current maximum, if specified, see dynamic_field_info below.
If successful this procedure will return E_OK, otherwise the following is returned:
E_BAD_ARGUMENT - NULL field pointer or field size is already greater than max_growth or max_growth is less than zero.
This procedure will work regardless of the setting of the O_STATIC option.
In order to allow the user to query the current size of the buffer, the following function is provided.
SYNOPSIS int dynamic_field_info(field, drows, dcols, max) FIELD*field; int *drows, *dcols, *max;
If successful this procedure will return E_OK, and drows and dcols will contain the actual number of rows and columns of field. If a maximum growth has been specified (see set_max_field above) for field, max will contain the specified growth limit, otherwise max will contain zero.
If field is NULL, drows, dcols, and max are unchanged and the following is returned:
E_BAD_ARGUMENT - NULL field pointer
This procedure will work regardless of the setting of the O_STATIC option.
Making a field dynamic by turning off the O_STATIC option will affect the field in the following ways:
1. If parameter nbuf in the original new_field library call is greater than zero, all additional buffers will grow simultaneously with buffer 0. Recall, buffer 0 is used by the system to store data entered by the user, nbuf can be used to request the allocation of additional buffers available to the application. The field buffers will grow in chunks of size buf_size = ((rows + nrow) * cols), the size of the original buffer minus one.
If a field is dynamic, the remainder of the forms library is affected in the following way.
1. The field option O_AUTOSKIP will be ignored if the option O_STATICis off and there is no maximum growth specified for the field. Currently, O_AUTOSKIP generates an automatic REQ_NEXT_FIELD form driver request when the user types in the last character position of a field. On a growable field with no maximum growth specified, there is no ``last'' character position. If a maximum growth is specified, the O_AUTOSKIP option will work as normal if the field has grown to its maximum size.
2. The field justification will be ignored if the option O_STATIC is off. Currently, set_field_just can be used to JUSTIFY_LEFT,JUSTIFY_RIGHT, JUSTIFY_CENTER the contents of a one line field. A growable one line field will, by definition, grow and scroll horizontally and may contain more data than can be justified. The return from field_just will be unchanged.
3. The overloaded form driver request REQ_NEW_LINE will operate the same way regardless of the O_NL_OVERLOAD form option if the field option O_STATIC is off and there is no maximum growth specified for the field. Currently, if the form option O_NL_OVERLOAD is on, REQ_NEW_LINE implicitly generates a REQ_NEXT_FIELD if called from the last line of a field. If a field can grow without bound, there is no last line, so REQ_NEW_LINE will never implicitly generate a REQ_NEXT_FIELD. If a maximum growth limit is specified and the O_NL_OVERLOAD form option is on, REQ_NEW_LINE will only implicitly generate REQ_NEXT_FIELD if the field has grown to its maximum size and the user is on the last line.
4. The library call dup_field will work as described in the section "Creating and Freeing Fields"; it will duplicate the field, including the current buffer size and contents of the field being duplicated. Any specified maximum growth will also be duplicated.
5. The library call link_field will work as described in the section "Creating and Freeing Fields"; it will duplicate all field attributes and share buffers with the field being linked. If the O_STATIC field option is subsequently changed by a field sharing buffers, how the system reacts to an attempt to enter more data into the field than the buffer will currently hold will depend on the setting of the option in the current field.
6. The library call field_info will work as described in the section "Obtaining Field Size and Location Information"; the variable nrow will contain the value of the original call to new_field. The user should use dynamic_field_info, described above, to query the current size of the buffer.
ETI provides the following function to move an existing disconnected field to a new location.
SYNOPSIS int move_field (field, firstrow, firstcol) FIELD * field; intfirstrow; int firstcol;
The following example shows one way you might use function move_field. Function shift_fields receives the int value updown, which it uses to change the row number of each field in a given field pointer array. You could, of course, shift the columns in like fashion.
Example Shifting All Form Fields a Given Number of Rows
void shift_fields (f, updown) FIELD ** f; int updown; /* signed number of rows to shift */ { int rows, cols, frow, fcol, nrow, nbuf; while (*f) { /* field_info fetches the values of the field parameters */ field_info (*f, &rows, &cols, &frow, &fcol, &nrow, &nbuf); move_field (*f, frow + updown, fcol); ++f; } }
See the previous section ``Obtaining Field Size and Location Information'' for more on field_info used in this example.
If successful, function move_field returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL field or firstrow/firstcol < 0 E_CONNECTED - connected field
ETI establishes initial current default values for field attributes. During field initialization, every field attribute is assigned the current default value for the attribute. As you can with menu functions, you can change or retrieve the current default attribute values by calling the appropriate function with a NULL field pointer. After the current default changes, every field created using new_field will have the new default value.
NOTE: Fields previously created do not have their attributes changed by changing the current system default.
Several of the following sections show how to change the default values for various field attributes.
Every field is created with the current default field type. The initial ETI default field type is a no_validation field. Any data may occupy it. (This default can be changed as described below.) To change a field's type from the default, ETI provides the following functions for manipulating a field's (data) type.
SYNOPSIS int set_field_type (field, type, [arg_1, arg_2, ...]) FIELD *field; FIELDTYPE * type; FIELDTYPE * field_type (field) FIELD * field; char * field_arg (field) FIELD * field;
The function set_field_type takes a FIELDTYPE pointer and a variable number of arguments depending on the field type. The field type ensures that the field is validated as your end-user enters characters into the field or attempts to leave it.
The form driver (described later in the section "Form Driver Processing") validates the data in a field only when data is entered by your end-user. Validation does not occur when
In all cases, validation occurs only if data is changed by passing data or making requests to the form driver. To make requests, your user enters characters or escape sequences mapped to commands that the form driver recognizes. See the section "Form Driver Processing" below.
If successful, set_field_type returns E_OK. If not, it returns the following:
E_SYSTEM_ERROR - system error
Function field_type returns the field type of the field, while function field_arg returns the field argument pointer. For more on the field argument pointer in programmer-defined field types, see the section "Supporting Programmer-defined Field Types" below.
If the function set_field_type is not applied to a field, the field type is the current default.
NOTE: Remember that the initial ETI default is not to validate the field at all - any kind of data may be entered into the field.
You can change the ETI default by giving function set_field_type a NULL field pointer. Suppose, for instance, that you want to change the system default field type to a minimum 10-character field of type TYPE_ALNUM. As described below, this field type accepts alphanumeric data - every entered character must be a digit or an alphabetic (not a special) character. You can write
set_field_type ((FIELD *) 0, TYPE_ALNUM, 10);
ETI provides several generic field types besides TYPE_ALNUM. Moreover, you can define your own field types,as described later in the section "Freeing Programmer-defined Field Types." The following sections describe all ETI generic field types.
The form driver restricts a field of this type to alphabetic data.
SYNOPSIS set_field_type (field, TYPE_ALPHA, width); int width; /* minimum token width */
TYPE_ALPHA takes one additional argument, the minimum width specification of the field. Note that when you previously create a field with function new_field, your cols argument is the maximum width specification of the field. With TYPE_ALPHA (and TYPE_ALNUM as well), your specification width must be less than or equal to cols. If not, the form driver cannot validate the field.
NOTE: TYPE_ALPHA does not allow blanks or other special characters.
To set a middlename field, for instance, to TYPE_ALPHA with a minimum of 0 characters (in effect, to make the end-user's completing the field optional), you can write
FIELD * middlename; set_field_type (middlename, TYPE_ALPHA, 0);
This type restricts the set field to alphanumeric data, alphabetic characters (upper- or lower-case) and digits.
SYNOPSIS set_field_type (field, TYPE_ALNUM, width); int width /* minimum token width */
Like TYPE_ALPHA, TYPE_ALNUM takes one additional argument, the field's minimum width specification.
NOTE: Like TYPE_ALPHA, TYPE_ALNUM does not allow blanks or other special characters.
To set a field, say partnumber, to receive alphanumeric data at least eight characters wide, you write
FIELD * partnumber; set_field_type (partnumber, TYPE_ALNUM, 8);
This field type enables you to restrict the valid data for a field to a set of enumerated values. The type takes three arguments beyond the minimum two that set_field_type requires.
SYNOPSIS set_field_type (field, TYPE_ENUM, keyword_list, checkcase, checkunique); char ** keyword_list; /* list of acceptable values */ int checkcase; /* check character case */ int checkunique; /* check for unique match */
The argument keyword_list is a NULL-terminated array of pointers to character strings that are the acceptable enumeration values. Argument checkcase is a Boolean flag that indicates whether upper- or lower-case is significant during match operations. Finally, checkunique is a Boolean flag indicating whether a unique match is required. If it is off and your end-user enters only part of an acceptable value, the validation procedure completes the field value automatically with the first matching value in the type. If it is on, the validation procedure completes the field value automatically only when enough characters have been entered to make a unique match.
To create a field, say response, with valid responses of yes (y) or no (n) in upper- or lower-case, you write:
char * yesno[] = { "yes", "no", (char *)0 }; FIELD * response; set_field_type (response, TYPE_ENUM, yesno, FALSE, FALSE);
The next example sets the last field (checkunique) to TRUE, which sets the TYPE_ENUM of field color to a list of colors.
Setting a Field to TYPE_ENUM of Colors
char * colors[13] = { "Black", "Charcoal", "Light Gray", "Brown", "Camel", "Navy", "Light Blue", "Hunter Green", "Gold", "Burgundy", "Rust", "White", (char *) 0 }; FIELD * color; set_field_type (color, TYPE_ENUM, colors, FALSE, TRUE);
Setting the field to TRUE requires the user to enter the seventh character of the color name in certain cases (Light Blue and Light Gray) before a unique match is made.
This type enables you to restrict the data in a field to integers.
SYNOPSIS set_field_type (field, TYPE_INTEGER, precision, vmin, vmax); int precision; /* width for left padding with 0's */ long vmin; /* minimum acceptable value */ long vmax; /* maximum acceptable value */
TYPE_INTEGER takes three additional arguments: a precision specification, a minimum acceptable value, and a maximum acceptable value.
As your end-user enters characters, they are checked for validity. A TYPE_INTEGER value is valid if it consists of an optional minus sign followed by some number of digits. As the end-user tries to leave the field, the range check is applied.
NOTE: If, contrary to possibility, the maximum value vmax is less than or equal to the minimum value vmin, the range check is ignored - any integer that fits in the field is valid.
If the range check is passed, the integer is padded on the left with zeros to the precision specification. For instance, if the current value were 18, a precision of 3 would display
018
whereas a precision of 4 would display
0018
For more on ETI's handling of precision, see the manual page printf(3S).
As an example of how to use set_field_type with TYPE_INTEGER, the following might represent a month, padded to two digits:
FIELD * month; set_field_type (month, TYPE_INTEGER, 2, 1L, 12L); /* displays single digit months with leading 0 */
Note the requirement that the minimum and maximum values be converted to type long with the L.
This type restricts the data for the set field to decimal numbers.
SYNOPSIS set_field_type (field, TYPE_NUMERIC, precision, vmin, vmax); int precision; /* digits to right of the decimal point */ double vmin; /* minimum acceptable value */ double vmax; /* maximum acceptable value */
TYPE_NUMERIC takes three additional arguments: a precision specification, a minimum acceptable value, and a maximum acceptable value.
As your end-user enters characters, they are checked for validity as decimal numbers. A TYPE_NUMERIC value is valid if it consists of an optional minus sign, some number of digits, a decimal point, and some additional digits.
The precision is not used in validation; it is used only in determining the output format. See printf(3S) for more on precision. As the end-user tries to leave the field, the range check is applied.
As with TYPE_INTEGER, if the maximum value is less than or equal to the minimum value, the range check is ignored.
For instance, to set a maximum value of $100.00 for a monetary field amount, you write:
FIELD * amount; set_field_type (amount, TYPE_NUMERIC, 2, 0.00, 100.00);
This type enables you to determine whether the data entered into a field matches a specific regular expression.
SYNOPSIS set_field_type (field, TYPE_REGEXP, expression); char *expression; /* regular expression */
TYPE_REGEXP takes one additional argument, the regular expression. See regcmp(3X) or Chapter 11 in the Programmer's Guide: ANSI C and Programming Support Tools for regular expression details.
Consider, for example, how you might create a field that represents a part number with an upper- or lower-case letter followed by exactly 4 digits:
FIELD * partnumber; set_field_type (partnumber, TYPE_REGEXP, "^[A-Za-z][0-9]{4}$");
Note that this example assumes the field is five characters wide. If not, you may want to change the pattern to accept blanks on either side, thus:
FIELD * partnumber; set_field_type (partnumber, TYPE_REGEXP, "^ *[A-Za-z][0-9]{4} *$");
Unlike menu items, which always occupy one line, form fields may occupy one or more lines (rows). Fields that occupy one line may be justified left, right, center, or not at all.
SYNOPSIS int set_field_just (field, justification) FIELD * field; int justification; int field_just (field) FIELD * field;
Fields that occupy more than one line are not justified because the data entered typically extends into subsequent lines. Justification is also ignored on a one line field if the O_STATIC option is off or the field was dynamic and has grown beyond its original size. See the section "Dynamically Growable Fields" for more detail.
Setting the number of field columns (cols) and the minimum width or precision does not always determine where the data fits in the field - there may be excess character space before or after the data. Function set_field_just lets you justify data in one of the following ways:
NO_JUSTIFICATION - no justification processing (initial default) JUSTIFY_LEFT - left justify value in field JUSTIFY_RIGHT - right justify value in field JUSTIFY_CENTER - center value in the field
No matter what the justification, fields are automatically left justified as your end-user enters data and edits the field. Once field validation occurs upon the user's request to leave the field, ETI justifies the field as specified.
For instance, to left justify a name field and right justify an amount field, you can write:
FIELD * name, * amount; set_field_just (name, JUSTIFY_LEFT); /* left justify a field */ set_field_just (amount, JUSTIFY_RIGHT); /* right justify a field */
If successful, set_field_just returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - bad justification
As with most other ETI functions, if one of these functions is passed a NULL field pointer, it assigns or fetches the system default. For instance, to change the system default from no justification to centering the value in its field, you write
set_field_just( (FIELD *) 0, JUSTIFY_CENTER); /* set new default */
The following functions enable you to set and read the pad character and the low-level ETI (curses) attributes associated with your field's foreground and background. The foreground attribute applies only to those field characters that represent data proper, while the background attribute applies to the entire field. SYNOPSIS
int set_field_fore (field, attr) FIELD * field; chtype attr; chtype field_fore (field) FIELD * field; int set_field_back (field, attr) FIELD * field; chtype attr; chtype field_back (field) FIELD * field; int set_field_pad (field, pad) FIELD * field; int pad; int field_pad (field) FIELD * field;
The initial default for both the foreground and background are A_NORMAL. (See the section on attribute descriptions earlier in this guide or the curses(3X) pages for more on screen attributes.) The pad character is the character displayed wherever a blank occurs in the field value stored in field buffer 0.
As an example, to change the background of a field total to A_UNDERLINE and A_STANDOUT, you write:
FIELD* total; set_field_back (total, A_UNDERLINE | A_STANDOUT);
If function set_field_fore or set_field_back encounter an error, they return one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - bad curses attribute
The function set_field_pad sets the field's pad character. The default pad character is a blank. During form processing, pad characters in the field are translated to blanks in the field's value.
NOTE: Because ETI does not distinguish between system-generated pad characters and those entered as data, be sure to choose your pad character so as not to conflict with valid data.
To set the pad character for field total to an asterisk (*), you write:
FIELD * total; set_field_pad (total, '*');
If successful, function set_field_pad returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - nonprintable pad character
As usual, you can change or access the ETI defaults. To change the default background to A_UNDERLINE, you write:
set_field_back ((FIELD *) 0, A_UNDERLINE);
ETI provides special features that promote development of a wide range of form applications. These include field buffers, field status flags, and field user pointers.
Recall that you set the number of additional buffers associated with a field upon its creation with new_field. Buffer 0 holds the value of the field. The following functions let you store values in the buffers and later read them.
SYNOPSIS int set_field_buffer (field, buffer, value) FIELD * field; int buffer; char * value; char * field_buffer (field, buffer) FIELD * field; int buffer
The parameter buffer should range from 0 through nbuf, where nbuf is the number of additional buffers in the new_field call. All buffers besides 0 may be used to suit your application.
If field in set_field_buffer is a dynamic field and the length of value is greater than the current buffer size, the buffer will expand, up to the specified maximum, if any, to accommodate value. See the section "Dynamically Growable Fields" for more detail on dynamic fields and setting a maximum growth. If the field is not dynamic or the length of value is greater than any specified maximum field size, then value may be truncated.
As an example, suppose your application kept a field's default value in field buffer 1. It could use the following code to reset the current field to its default value.
#define VAL_BUF 0 #define DFL_BUF 1 void reset_current (form) FORM * form; { /* set f to current field, described in section "Manipulating the Current Field" below */ FIELD * f = current_field (form); /* set field f to default value */ set_field_buffer (f, VAL_BUF, field_buffer (f, DFL_BUF)); }
If successful, set_field_buffer returns E_OK. If not, it returns one of the following: e
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL fieldpointer, NULL value, or buffer out of rang
Function field_buffer(), however, returns NULL if its fieldpointer is NULL or buffer is out of range.
The function field_buffer() always returns the correct value if the field is not current. However, if the field is current, the function is sometimes inaccurate because data is not moved to field buffer 0 immediately upon entry. You may rest assured that field_buffer() is accurate on the current field if
See the sections "Creating a Field Type with Validation Functions" "Establishing Field and Form Initialization and Termination Routines" and "Field Validation Requests" below for details on these routines.
Every field has an associated status flag that is set whenever the field's value (field buffer 0) changes. The following functions enable you to set and access this flag.
SYNOPSIS int set_field_status (field, status) FIELD * field; int status; int field_status (field) FIELD * field;
The field status is TRUE if set or FALSE if cleared. By default, the field status is FALSE when the field is created.
These routines promote increased efficiency where processing need occur only if a field has been changed since some previous state. Two examples are undo operations and database updates. Function update example for instance, loops through your field pointer array to save the data in each field if it has been changed (if its field_status is TRUE).
Using the Field Status to Update a Database
void update (form) FORM * form; void save_field_data (f) FIELD * f; { char * data = field_buffer (f, 0); /* fetch data in field */ /* save data */ } { FIELD ** f = form_fields (form); /* fetch pointer to field pointer array */ while (*f) { if (field_status (*f)) /* field data changed ? */ { save_field_data (*f); /* yes, save new data */ set_field_status (*f, FALSE); /* set field status back */ } ++f; } }
If successful, set_field_status() returns E_OK. If not, it returns the following:
E_SYSTEM_ERROR - system error
The initial ETI default field status is clear. As always, you can change the default by passing set_field_status() a NULL field pointer.
Like the function field_buffer(), function field_status() always returns the correct value if the field is not current. However, if the field is current, the function is sometimes inaccurate because the status flag is not set immediately. You may rest assured that field_status() is accurate on the current field if
See the sections "Creating a Field Type with Validation Functions" "Establishing Field and Form Initialization and Termination Routines" and "Field Validation Requests" below for details on these routines.
As it does with panels and menus, ETI provides functions to manipulate an arbitrary pointer convenient for field data such as title strings, help messages, and the like.
SYNOPSIS
SYNOPSIS int set_field_userptr (field, userptr) FIELD * field; char *userptr; char * field_userptr (field) FIELD * field;
You can connect an application-defined structure to the field using this pointer. By default, the field user pointer is NULL.
The following example shows three routines that use these field functions:
set_field_id()
allocates space for a struct ID to be associated with a field and calls set_field_userptr() to establish the field's pointer to it
free_field_id()
frees the space for the associated ID
find_field()
searches the names associated with all fields on the form to determine whether any of them match an arbitrary name passed to it
Using the Field User Pointer to Match Items
#define match(a,b)(strcmp (a, b) == 0) typedef struct { int type; char * name; } ID; /* to be hooked onto field userptr */ void set_field_id (f, type, name) /* associate type and name with field f */ FIELD * f; int type; char * name; { ID * id = (ID *) malloc (sizeof (ID)); /* allocate space, see malloc(3X) */ if (id) /* if space allocated */ { id -> type = type; /* assign type and name */ id -> name = name; } set_field_userptr (f, (char *) id); /* point to id */ } void free_field_id (f) /* free id connected tofield */ FIELD * f; { x = (ID *) field_userptr (*f); /* fetch field user pointer */ if (x) free (x); } FIELD * find_field (f, name) /* find field on form with name */ FORM * form; char * name; { FIELD ** f = form_fields (form); /* fetch pointer to form's field array */ ID * x; while (*f) / * for each field in the form */ { x = (ID *) field_userptr (*f); /* fetch ID associated with field */ if (x && x -> name && match (name, x -> name)) /* does its name match ? */ break; ++f; } return *f; /* return field pointer of match or NULL*/ }
Note that if a match is not found, find_field returns a NULL field pointer. See the previous sections on panel and menu user pointers for more examples.
If successful, set_field_userptr returns E_OK. If not, it returns the following:
E_SYSTEM_ERROR - system error
To change the system default user pointer from NULL to one of your choice, you need only pass set_field_userptr a NULL field pointer. Passing a NULL field pointer to field_userptr returns the current default user pointer.
ETI provides several field options for controlling how data is entered and displayed in a field. The following functions let you set or clear these options or read their settings.
SYNOPSIS int set_field_opts (field, opts) FIELD * field; OPTIONS opts; OPTIONS field_opts (field) FIELD * field; options: O_VISIBLE O_ACTIVE O_PUBLIC O_EDIT O_WRAP O_BLANK O_AUTOSKIP O_NULLOK O_PASSOK O_STATIC
Function set_field_opts turns off all options that do not appear in its second argument. By default, all options are on.
The field options and their effects are as follows:
O_VISIBLE
determines field visibility. If this option is on, the field is displayed. If this option is off, it is erased. This option is useful for supporting pop-up fields, fields visible or not depending on another field's value.
O_ACTIVE
determines if a field is visited during form processing. If inactivated, the field is ignored during form processing. Inactive fields enable you to create field labels and other static form symbols or changeable symbols that are not affected during form processing. Examples of fields that change value but are not affected during form processing are row and column totals, as in a spreadsheet program. You can change field values using calls to set_field_buffer.
O_PUBLIC
determines how feedback is presented to the user as data is entered. The data in public fields is displayed as entered, while the data in non-public fields is not displayed at all. Further, in non-public fields, the cursor does not actually move across the field, but the forms subsystem internally maintains the cursor position relative to the field data. You can use non-public fields to implement password fields.
O_EDIT
determines if field editing is permitted. By default, this option is on and a field may be edited. If the O_EDIT option is off, the field may be visited but not changed. Editing requests or attempts to enter data will fail. (REQ_PREV_CHOICE and REQ_NEXT_CHOICE requests, however, are honored, if they are defined for the field's type.) This is useful for creating fields for browsing such as scrollable help messages.
O_WRAP
determines if word wrapping occurs at the end of each line of the field. If any character of the word does not fit on the line as it is entered, the entire word is automatically moved to the beginning of the next line, if there is one. If the O_WRAP option is off, the word is split between the two lines.
O_BLANK
determines if the whole field is automatically erased when the end-user types a character in the first character position of the field before any character position has been changed. If the O_BLANK option is off, this does not occur.
O_AUTOSKIP
determines how the field responds when it becomes full. Ordinarily, when a field is full, an automatic request to move to the next field on the form is generated. If, however, the O_AUTOSKIP option is off, the end-user remains at the end of the field.
The O_AUTOSKIP option will be ignored if the option O_STATIC is off and there is no maximum growth specified for the field. On a growable field with no maximum growth specified, there is no "last'' character position. If a maximum growth is specified, the O_AUTOSKIP option will cause an REQ_NEXT_FIELD to be generated from the last character position if the field has grown to its maximum size.
O_NULLOK
determines how the field responds when your end-user tries to leave a blank field. By default, this option is on - when a field is blank, a request to leave the field is honored without validating the field. If, on the other hand, the O_NULLOK option is off, the validation procedure is applied to the blank field.
O_PASSOK
When this option is on, the field is checked for validity only if your end-user entered data into the field or edited it. If it is off, the validity check occurs whenever your user leaves the field, whether or not the field was changed. This is useful for fields whose validation function may change dynamically.
O_STATIC
When this option is on, the field is fixed in size and any attempt to add more data than the current field buffer will hold will fail. If it is off, the field will grow dynamically to accommodate additional data entered by the user. See the section "Dynamically Growable Fields'' for more information on dynamic fields.
Remember that options are Boolean values. So to turn off option O_ACTIVE for field f0 and to turn it on for field f1, you use the Boolean operators and write:
FIELD * f0, * f1; set_field_opts (f0, field_opts (f0) & ~O_ACTIVE); /* turn option off */ set_field_opts (f1, field_opts (f1) | O_ACTIVE); /* turn option on */
NOTE: Although you can change field option settings on posted forms, you cannot change option settings for the current field.
ETI also provides the following two functions which let you turn a field option on or off without using function field_opts().
SYNOPSIS int field_opts_on (field, opts) FIELD * field; OPTIONS opts; int field_opts_off (field, opts) FIELD * field; OPTIONS opts;
Unlike function set_field_opts(), these functions leave unnamed option settings intact.
As an example, the following code turns options O_BLANK and O_AUTOSKIP off for field f0 and on for field f1:
FIELD * f0, * f1; field_opts_off (f0, O_BLANK | O_AUTOSKIP); /* turn options off */ field_opts_on (f1, O_BLANK | O_AUTOSKIP); /* turn options on */
If successful, functions set_field_opts(), field_opts_on(), and field_opts_off() return E_OK. If not, they return the following:
E_SYSTEM_ERROR - system error E_CURRENT- cannot change current field options
As usual, you can change the ETI default option settings by passing function set_field_options(), field_opts_on(), or field_opts_off() a NULL field pointer. Calling field_opts() with a NULL field pointer returns the system default.
Once you have established a set of fields and their attributes, you are ready to create a form to contain them.
SYNOPSIS FORM * new_form (fields) FIELD ** fields; int free_form (form) FORM * form;
The function new_form() takes as an argument a NULL-terminated, ordered array of FIELD pointers that define the fields on the form. The order of the field pointers determines the order in which the fields are visited during form driver processing discussed below.
As with the comparable ETI menu function new_menu(), function new_form() does not copy the array of field pointers.Instead, it saves the pointer to the array. Be sure not to change the array of field pointers once it has been passed to new_form(), until the form is freed by free_form() or the field array replaced by set_form_fields() described in the next section.
Fields passed to new_form() are connected to the resulting form.
NOTE: Fields may be connected to only one form at a time.
To connect fields to another form, you must first disconnect them using free_form() or set_form_fields(). If fields is NULL, the form is created but no fields are connected to it.
Unlike menus, ETI forms are logically divided into pages. Two functions enable you to mark a field that is to start a new page and to return a Boolean value indicating whether a given field does so.
SYNOPSIS int set_new_page(field, bool) FIELD * field; int bool; /* TRUE or FALSE */ int new_page(field) FIELD * field;
The initial system default value of new_page() is FALSE. This means that, unless specified with set_new_page(), each field is assumed to continue the current page.
NOTE: In general, you should make the size of each form page smaller than the form's window size.
If function set_new_page() executes successfully, it returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR -system error E_CONNECTED -field connected to form
The following example shows how to create a simple two-page form.
Creating a Form
FIELD * f[7]; FORM * form; /* create fields as described in section "Creating and Freeing Fields" above */ f[0] = new_field (...); /* 1st field on page 1 */ f[1] = new_field (...); /* 2nd field on page 1 */ f[2] = new_field (...); /* 3rd field on page 1 */ f[3] = new_field (...); /* 4th field on page 1 */ f[4] = new_field (...); /* 1st field on page 2 */ f[5] = new_field (...); /* 2nd field on page 2 */ f[6] = (FIELD *) 0; /* signal end of form */ set_new_page (f[4], TRUE); /* start new page with fifth field f[4] */ form = new_form (f); /* create the form */
If successful, new_form() returns a pointer to the new form. If there is no memory available for the form or one of the given fields is connected to another form, new_form() returns NULL. Undefined results occur if the array of field pointers is not NULL- terminated.
The function free_form disconnects all fields and frees any space allocated for the form. Its argument is a form pointer previously obtained from new_form. The fields themselves are not automatically freed.
NOTE: You should free the fields comprising a form using free_field() only after you free their form using free_form().
If successful, free_form returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_POSTED - form is posted
Posting forms is described below.
As with panel, item, menu, and field pointers, form pointers should not be used once they are freed. If they are, undefined results occur.
Recall that an attribute is any feature whose value can be set or read by an appropriate ETI function. A form attribute is any form feature whose value can be set or read by an appropriate ETI function. The set of fields connected to a form and the number of fields connected to it are examples of form attributes.
Once you create a form with one set of fields using new_form, you can change the fields connected to it.
SYNOPSIS int set_form_fields (form, fields) FORM * form; FIELD ** fields; FIELD ** form_fields (form) FORM * form;
Like new_form(), function set_form_fields() takes as an argument a NULL-terminated, ordered array of FIELD pointers that define the fields on the form and determine the order in which the fields are visited during form driver processing.
When set_form_fields() is called, the fields previously connected to the form are disconnected from it (but not freed) before the new fields are connected.
Like any set of fields connected to a form, the new fields cannot be passed to other forms while they are connected to the given form. You must first disconnect them by calling free_form() or again calling set_form_fields().
There are two ways to disconnect the fields associated with a form without connecting another set of fields to the form:
The first method frees the space allocated for the form, whereas the second does not.
To change the fields associated with form to those referenced in array pointer newfields, you can write:
FORM * form; FIELD ** newfields; set_form_fields (form, newfields); /* associate new set of fields with form */
If function set_form_fields() encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_POSTED - form is posted E_CONNECTED - connected field
Posting forms is discussed in the section "Posting and Unposting Forms'' below.
The function form_fields() returns the array of field pointers defining the form's fields. The function returns NULL if no fields are connected to the form or the form pointer is NULL.
The following function returns the number of fields connected to the given form.
SYNOPSIS int field_count (form) FORM * form;
If form is NULL, field_count() returns -1.
As an example, consider the following routine, which determines whether your user is on the last field of the form as numbered in the field pointer array:
int on_last_field (form) FORM * form; { /* fetch number of last field */ int lastindex = field_count (form) - 1; /* determine whether number of current field is the same */ return field_index (current_field (form)) == lastindex; }
Note the use of functions field_index() and current_field(), described below in the section "Manipulating the Current Field"
It may be desirable to indicate to the user whether there is additional data either ahead or behind in a scrollable field. It is the responsibility of application developers to indicate, however they like, the presence of off screen data. The following functions allow the developer to query the presence of offscreen data.
SYNOPSIS int data_ahead(form) FORM *form; int data_behind(form) FORM *form;
data_ahead() returns TRUE, if there is either more data offscreen to the right if the current field is a one line field, or more data offscreen below if the current field is multiline. Otherwise FALSE is returned. Data is defined to be any non-pad character; see the section "Setting the Field Foreground, Background, and Pad Character" for more detail on the pad character.
data_behind() returns TRUE, if the first character position of the current field is not currently being displayed. Otherwise FALSE is returned.
During form initialization using new_form(), all form attributes are assigned default values. As you can with menu attributes, you can change these default attribute values by calling the appropriate function with a NULL form pointer as its first argument. All subsequent forms created using new_form() will then have the new default attribute value. However, forms created before the change to the current default value will retain the initial values of their attributes. Several examples of changing default values occur throughout the rest of this chapter.
In general, to display a form, you determine the form dimensions, optionally associate a window and subwindow with the form, post the form, and refresh the screen.
Every form is associated with a window and subwindow.
NOTE: By default, (1) the form window is NULL, which by convention means that ETI uses stdscr as the form window; and (2) the form subwindow is NULL, which means that ETI uses the form window as the form subwindow.
Windows are used to create borders, titles, and the like. Before ETI posts a form, it must determine the sizes of its window and subwindow.
To determine the minimum window or subwindow size for a form, ETI considers the following:
By automatically fetching this information previously established by calls to new_field(), function scale_form() saves you the effort of calculating the size of your form subwindow.
Considering the above information, this function returns the minimum window size necessary for containing the form.
SYNOPSIS int scale_form (form, rows, cols) FORM * form; int * rows; int *cols;
Because function scale_form() must return more than one value (namely, the minimum number of rows and columns for the form) and C passes parameters "by value'' only, the arguments of scale_form() are pointers. The pointer arguments rows and cols point to locations used to return the minimum number of rows and columns for the form.
NOTE: You should call scale_menu() only after the form's fields have been connected to the form using new_form() or set_form_fields().
As an example, to return the minimum (sub)window size for form f in variables rows and cols, you can write:
FORM * form; int rows, cols; /* create fields create form */ /* determine minimum row and column size */ scale_form (form, &rows, &cols); /* create form subwindow, as described in next section */
If function scale_form() encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_NOT_CONNECTED - no fields connected to the form
Remember that two windows are associated with every form - the form window and the form subwindow. The following functions assign windows and subwindows to forms and fetch those previously assigned to them.
SYNOPSIS int set_form_win (form, window) FORM * form; WINDOW * window; WINDOW * form_win (form) FORM * form; int set_form_sub (form, window) FORM * form; WINDOW * window; WINDOW * form_sub (form) FORM * form;
These functions enable you to place stylistic borders, titles, and other decoration around a form.
NOTE: Remember that if the form window is NULL (the default), ETI uses stdscr. If the form subwindow is NULL (the default), ETI uses the form window so you need not use functions set_form_win() or set_form_sub() at all.
If you do not want to use stdscr, you should create a window and a subwindow for every form. ETI automatically writes all low-level ETI (curses) output of the form proper on the form subwindow. If you want further output (such as borders, titles, or static messages), you should write it on the form window. However, you need not write any further output at all.
NOTE: Be sure to apply all low-level ETI (curses(3X)) command output and refresh operations to your form's window, not its subwindow.
The following figure diagrams the relationship between ETI Form functions, your application program, and its form window and subwindow.
The following example shows how to create a form with a border of the terminal's default vertical and horizontal characters.
Creating a Border Around a Form
/* create window 4 characters larger than form dimensions with top left corner at (0, 0). subwindow is positioned at (2, 2) relative to the form window origin with dimensions equal to the form dimensions. */ FORM * f; WINDOW * w; int rows, cols; scale_form (f, &rows, &cols); /* get dimensions of form */ if (w = newwin (rows+4, cols+4, 0, 0)) { set_form_win (f, w); /* associate window and subwindow with form */ set_form_sub (f, derwin (w, rows, cols, 2, 2)); box (w, 0, 0); /* create border */ }
Function scale_form() sets the values of the variables rows and cols, which provide the form dimensions without the border. Adding four to the dimensions of the form window clearly sets off the form border from the fields of the form (the form proper).
If functions set_form_win() or set_form_sub() encounter an error, they return one of the following:
E_SYSTEM_ERROR - system error E_POSTED - form is posted
As usual, you can change the default form window or subwindow. For instance, you can change the default form window from stdscr to a window w by passing a NULL formpointer, as follows:
int rows, cols, firstrow, firstcol; /* create form window */ WINDOW * w = newwin (rows, cols, firstrow, firstcol); set_form_win((FORM *)0, w); /* change default form window to w */
Note that if you later change a posted form by writing directly to its window, before continuing you must reposition the form window cursor using pos_form_cursor(). See the section "Positioning the Form Cursor" below.
When you have created a form and its window and subwindow, you are ready to post it. To post a form is to display it on the form's subwindow; to unpost a form is to erase it from the form's subwindow.
SYNOPSIS int post_form (form) FORM * form; int unpost_form (form) FORM * form;
Unposting a form does not remove its data structure from memory.
NOTE: To post a form, be sure that you have connected fields to it first.
The following uses two application routines, display_form() and erase_form(), to show how you might post and later unpost a form. The code builds on that used previously to create the form's window and subwindow.
Posting and Unposting a Form
static void display_form (f) /* create form windows and post */ FORM * f; { WINDOW * w; int rows; int cols; scale_form (f, &rows, &cols); /* get dimensions of form */ /* create form window */ if (w = newwin (rows+4, cols+4, 0, 0)) { set_form_win (f, w); set_form_sub (f, derwin (w, rows, cols, 2, 2)); box (w, 0, 0); keypad (w, 1); } else /* error routine in previous section "ETI Low-level Interface to High-level Functions" */ error ("error return from newwin", NULL); if (post_form (f) != E_OK) /* post form */ error ("error return from post_form", NULL); else refresh (w); } static void erase_form (f)/* unpost and delete form windows */ FORM * f; { WINDOW * w = form_win (f); WINDOW * s = form_sub (f); unpost_form (f); /* unpost form */ werase (w); /* erase form window */ wrefresh (w); /* refresh screen */ delwin (s); /* delete form windows */ delwin (w); }
If successful, function post_form() returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_POSTED - form is already posted E_NOT_CONNECTED - no connected fields E_NO_ROOM - form does not fit in subwindow
If successful, the function unpost_form() returns E_OK. If not, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_NOT_POSTED - form is not posted E_BAD_STATE - called from init/term function
The initialization and termination routines are discussed in the next section.
Like the function menu_driver() for the menu subsystem, function form_driver() is the workhorse of the form system. Once the form is posted, the form driver handles all interaction with your end-user. The form driver responds to
Your application passes a character to the form driver for processing and evaluates the results.
SYNOPSIS int form_driver (form, c) FORM * form; int c;
As with menu processing, to enable the form driver to process your end-users' requests, you must write an input key virtualization routine. This routine defines a correspondence between input keys, control characters, and escape sequences on the one hand and ETI form requests on the other. The routine returns a specific form request or application command that the form driver can process. Upon return from the form driver, your application can check if the input was processed appropriately. If not, it can specify actions to be taken. These may include terminating interaction with the form, responding to help requests, generating an error message, and so on.
For a sample virtual key mapping, consider the following, which contains the application-defined function get_request(). Most of the values returned by get_request() are ETI form requests defined in header file form.h and described in the next section. The other values returned (in this example, only value QUIT are defined by the application program treated in the later section "Calling the Form Driver"
A Sample Key Virtualization Routine
/* The following key mapping is defined by get_request. Note that ^X represents the character control-X. ^Q - end form processing ^F - move to next page ^B - move to previous page ^N - move to next field ^P - move to previous field home key - move to first field home down - move to last field ^L - move left to field ^R - move right to field ^U - move up to field ^D - move down to field ^W - move to next word ^T - move to previous word ^S - move to beginning of field data ^E - move to end of field data left arrow - move left in field right arrow - move right in field down arrow - move down in field up arrow - move up in field ^M <CR> - enter new line ^I - insert blank character ^O - insert blank line ^V - delete character ^H <BS> - delete previous character ^Y - delete line ^G - delete word ^C - clear to end of line ^K - clear to end of field ^X - clear entire field ^A - request next field choice ^Z - request previous field choice ESC - toggle between insert and overlay mode define application commands */ #define QUIT (MAX_COMMAND + 1) static int get_request (w)/* virtual key mapping */ WINDOW * w; { static int mode= REQ_INS_MODE; int c = wgetch (w);/* read a character */ switch (c) { case 0x11: /* ^Q */return QUIT; case 0x06: /* ^F */return REQ_NEXT_PAGE; case 0x02: /* ^B */return REQ_PREV_PAGE; case 0x0e: /* ^N */return REQ_NEXT_FIELD; case 0x10: /* ^P */return REQ_PREV_FIELD; case KEY_HOME: return REQ_FIRST_FIELD; case KEY_LL: return REQ_LAST_FIELD; case 0x0c: /* ^L */return REQ_LEFT_FIELD; case 0x12: /* ^R */return REQ_RIGHT_FIELD; case 0x15: /* ^U */return REQ_UP_FIELD; case 0x04: /* ^D */return REQ_DOWN_FIELD; case 0x17: /* ^W */return REQ_NEXT_WORD; case 0x14: /* ^T */return REQ_PREV_WORD; case 0x13: /* ^S */return REQ_BEG_FIELD; case 0x05: /* ^E */return REQ_END_FIELD; case KEY_LEFT: return REQ_LEFT_CHAR; case KEY_RIGHT: return REQ_RIGHT_CHAR; case KEY_DOWN: return REQ_DOWN_CHAR; case KEY_UP: return REQ_UP_CHAR; case 0x0d: /* ^M */return REQ_NEW_LINE; case 0x09: /* ^I */return REQ_INS_CHAR; case 0x0f: /* ^O */return REQ_INS_LINE; case 0x16: /* ^V */return REQ_DEL_CHAR; case 0x08: /* ^H */return REQ_DEL_PREV; case 0x19: /* ^Y */return REQ_DEL_LINE; case 0x07: /* ^G */returnREQ_DEL_WORD; case 0x03: /* ^C */return REQ_CLR_EOL; case 0x0b: /* ^K */return REQ_CLR_EOF; case 0x18: /* ^X */return REQ_CLR_FIELD; case 0x01: /* ^A */return REQ_NEXT_CHOICE; case 0x1a: /* ^Z */return REQ_PREV_CHOICE; case 0x1b: /* ESC */ if (mode == REQ_INS_MODE) return mode = REQ_OVL_MODE; else return mode = REQ_INS_MODE; } return c; }
In get_request(), only a subset of the requests are defined so that the requests your end-user can make are limited. If you like, you can also map two or more keys onto one request. This is helpful where some terminals lack one of the keys in question. In that case, the user can press the other key to the same effect.
Function get_request() first sets the data entry mode for the end-user. Here it is set initially to insert mode. The last case statement in the routine enables your end-user to press the escape key ESC to switch to overlay mode. Both modes are discussed in the "Field Editing Requests" section below.
Next, get_request() calls wgetch() to read a character entered by the user. The switch() statement maps the character read onto a specific application command or form request. The application command QUIT appears here as the first case; the other cases map characters onto form requests. Any character that is not an application command or form request is simply returned unchanged-it is treated as data being entered into the current field.
Note that this key mapping assumes your end-user will be using a terminal with arrow keys (KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN), a home key(KEY_HOME), and a home down key (KEY_LL).
The ETI form subsystem places the following requests at your application program's disposal.
These requests enable your end-user to navigate or move from page to page on a multi-page form.
REQ_NEXT_PAGE - move to next page REQ_PREV_PAGE - move to previous page REQ_FIRST_PAGE - move to first page REQ_LAST_PAGE - move to last page
Page navigation requests are cyclic so that
These requests enable your end-user to move from field to field on the current page of a single form. All field navigation requests are cyclic on the current page so that
REQ_NEXT_FIELD - move to next field REQ_PREV_FIELD - move to previous field REQ_FIRST_FIELD - move to first field REQ_LAST_FIELD - move to last field REQ_SNEXT_FIELD - move to sorted next field REQ_SPREV_FIELD - move to sorted previous field REQ_SFIRST_FIELD - move to sorted first field REQ_SLAST_FIELD - move to sorted last field REQ_LEFT_FIELD - move left to field REQ_RIGHT_FIELD - move right to field REQ_UP_FIELD - move up to field REQ_DOWN_FIELD - move down to field
All field navigation requests are cyclic on the current page so that
and so forth. The order of the fields in the field array passed to new_form() determines the order in which the fields are visited using the REQ_NEXT_FIELD, REQ_PREV_FIELD, REQ_FIRST_FIELD, and REQ_LAST_FIELD requests.
NOTE: Remember that the order of fields in the form array is simply the order in which fields are processed during form processing. This order bears no necessary relation to the order of the fields as they are displayed on the form page.
Your end-user may also move from field to field on the form page in row-major order - left to right, top to bottom. To do so, you use the REQ_SNEXT_FIELD, REQ_SPREV_FIELD,REQ_SFIRST_FIELD, and REQ_SLAST_FIELD requests.
Finally, your end-user can move about in different directions using the REQ_LEFT_FIELD, REQ_RIGHT_FIELD, REQ_UP_FIELD, and REQ_DOWN_FIELD requests. Note that the first character (top left corner) of the field is used to determine where the field is located relative to other fields. This means, for example, that a multi-line field whose first character is on the second row of a form is not on the same row as a field whose first character is on the third row of a form even though the multi- line field may extend below the third row.
These requests let your end-user move about inside a field. They may generate implicit scrolling operations on scrollable fields.
REQ_NEXT_CHAR - move to next character in field REQ_PREV_CHAR - move to previous character infield REQ_NEXT_LINE - move to next line in field REQ_PREV_LINE - move to previous line in field REQ_NEXT_WORD - move to next word in field REQ_PREV_WORD - move to previous word in field REQ_BEG_FIELD - move to beginning of field REQ_END_FIELD - move after last character in field REQ_BEG_LINE - move to beginning of line REQ_END_LINE - move after last character in line REQ_LEFT_CHAR - move left in field REQ_RIGHT_CHAR - move right in field REQ_UP_CHAR - move up in field REQ_DOWN_CHAR - move down in field
The effect of these requests is as follows:
These requests set the editing mode - insert or overlay.
REQ_INS_MODE - begin insert mode REQ_OVL_MODE - begin overlay mode
In insert mode(the default), all text is inserted at the current cursor position, while all existing text starting at the current cursor position is moved to the right. In overlay mode, text entered by your end-user overlays (replaces) existing text in the field. In both modes, the cursor is advanced one character position as each character is entered.
The following requests provide a complete set of field editing requests.
REQ_NEW_LINE - new line request REQ_INS_CHAR - insert blank character at cursor REQ_INS_LINE - insert blank line at cursor REQ_DEL_CHAR - delete character at cursor REQ_DEL_PREV - delete character before cursor REQ_DEL_LINE - delete line at cursor REQ_DEL_WORD - delete word at cursor REQ_CLR_EOL - clear to end of line REQ_CLR_EOF - clear to end of field REQ_CLR_FIELD - clear entire field
The effects of REQ_NEW_LINE and REQ_DEL_PREV requests depend on several factors such as the current mode (insert or overlay) and the cursor position within the field.
Because the requests REQ_NEW_LINE and REQ_DEL_PREV automatically do a request REQ_NEXT_FIELD or REQ_PREV_FIELD as described,they are said to be overloaded field editing requests. See the remarks on options O_NL_OVERLOAD and O_BS_OVERLOAD in the section "Setting and Fetching Form Options" below.
Fields can scroll if they have offscreen data. A field can have offscreen data if it was originally created with offscreen rows-the parameter nrow in the new_field() library routine was greater than 0-or the field has grown larger than its original size. See the section "Dynamically Growable Fields" for more details on the growth of fields.
There are two kinds of scrolling fields, vertically scrolling fields and horizontally scrolling fields. Multiline fields with offscreen data scroll vertically and one line fields with offscreen data scroll horizontally. Recall the library routine new_field(); a new field created with rows set to one and nrow set to zero will be defined to be a one line field. A new field created with rows + nrow greater than one will be defined to be a multiline field.
The following form driver requests are used on vertically scrolling multiline fields.
REQ_SCR_FLINE - scroll field forward a line REQ_SCR_BLINE - scroll field backward a line REQ_SCR_FPAGE - scroll field forward a page REQ_SCR_BPAGE - scroll field backward a page REQ_SCR_FHPAGE - scroll field forward half a page REQ_SCR_BHPAGE - scroll field backward half a page
In the descriptions above, a page is defined to be the number of visible rows of the field as displayed on the form.
The following form driver requests are used on horizontally scrolling one line fields.
REQ_SCR_FCHAR - scroll field forward a character REQ_SCR_BCHAR - scroll field backward a character REQ_SCR_HFLINE - scroll field forward a line REQ-SCR_HBLINE - scroll field backward a line REQ_SCR_HFHALF - scroll field forward half a line REQ_SCR_HBHALF - scroll field backward half a line
In the descriptions above, a line is defined to be the width of the field as displayed on the form.
In addition, intra-field navigation requests may generate implicit scrolling on scrollable fields. See the section "Intra-field Navigation Requests" above.
This request supports field validation for those field types that have it.
REQ_VALIDATION - validate current field
NOTE: In general, the ETI form driver automatically performs validation on a field before the user leaves it. (If your user leaves a field, it is valid.) However, before your user terminates interaction with the form, you should make the REQ_VALIDATION request to validate the current field.
Recall that on current fields, the values returned by functions field_buffer() and field_status() are sometimes inaccurate. (See the previous sections "Setting and Reading Field Buffers" and "Setting and Reading the Field Status") If, however, you make request REQ_VALIDATION immediately before calling these functions, you can be sure that the values they return are accurate-they agree with what your end-user has entered and appears on the screen.
The following requests enable your user to request the next or previous value of a field type.
REQ_NEXT_CHOICE - display next field choice REQ_PREV_CHOICE - display previous field choice
TYPE_ENUM is the only generic field type that supports these choice requests. In addition, programmer-defined field types may support these requests. See the previous section "Setting the Field Type to Ensure Validation" and the forthcoming section "Creating and Manipulating Programmer-defined Field Types" for information on these field types.
Form requests are implemented as integers above the low-level ETI (curses) maximum key value KEY_MAX. A symbolic constant MAX_COMMAND is provided so applications can implement their own commands without conflicting with the ETI form or menu subsystems. All ETI system form requests are greater than KEY_MAX and less than or equal to MAX_COMMAND.You should set your application-defined commands to an integer greater than MAX_COMMAND.
The ETI form driver works very much like the ETI menu driver. As soon as the form driver receives a request, it checks if it is an ETI form request. If so, it performs the request and reports the results. If the request is not an ETI form request, the form driver checks if the character is data, i.e., a printable ASCII character. If it is, it enters the character at the current position in the current field. If the character is not recognized as a form request or data, the form driver assumes the character is an application-defined command and returns E_UNKNOWN_COMMAND.
To illustrate a sample design for calling the form driver, we will consider a program that permits interaction with a sweepstakes entry form reproduced in the the following figure.
You have already seen much of the sweepstakes program in previous examples. The following shows its remaining routines.
An Example of Form Driver Usage
/* This program displays a sweepstakes entry form. */ #include <string.h> #include <form.h> static void start_curses() /* see the previous section "ETI Low-level Interface to High-level Functions" */ static void display_form (f) /* create form windows and post */ static void erase_form (f) /* unpost and delete form windows */ /* define application commands */ #define QUIT (MAX_COMMAND + 1) static int get_request (w)/* virtual key mapping */ static int my_driver (form, c) /* handle application commands */ FORM * form; int c; { switch (c) { case QUIT: /* validate current field */ if (form_driver (form, REQ_VALIDATION) == E_OK) return TRUE; break; } beep (); /* signal error */ return FALSE; } main (argc, argv) int argc; char * argv[]; { WINDOW * w; FORM * form; FIELD ** f; FIELD ** make_fields (); void free_fields (); int c, done = FALSE; PGM = argv[0]; if (! (form = new_form (make_fields ()))) error ("error return from new_form", NULL); start_curses (); display_form (form); /* interact with user */ w = form_win (form); while (! done) { switch (form_driver (form, c = get_request (w))) { case E_OK: break; case E_UNKNOWN_COMMAND: done = my_driver (form, c); break; default: beep ();/* signal error */ break; } } /* terminate form processing */ erase_form (form); end_curses (); f = form_fields (form); free_form (form); free_fields (f); exit (0); } typedef FIELD * (* PF_field ) (); typedef struct /* define struct for creation */ { PF_field type; /* field constructor*/ int rows; /* number of rows*/ int cols; /* number of columns*/ int frow; /* first row*/ int fcol; /* first column*/ char * v; /* field value*/ } FIELD_RECORD; static FIELD * LABEL (x) /* create a LABEL field */ FIELD_RECORD * x; { FIELD * f = new_field (1, strlen (x->v), x->frow, x->fcol, 0, 0); if (f) { set_field_buffer (f, 0, x->v); field_opts_off (f, O_ACTIVE); } return f; } static FIELD * STRING (x)/* create a STRING field */ FIELD_RECORD * x; { FIELD * f = new_field (x->rows, x->cols, x->frow, x->fcol, 0, 0); if (f) set_field_back (f, A_UNDERLINE); return f; } /* field definitions */ static FIELD_RECORD F [] = { LABEL, 0, 0,0,11,"Sweepstakes Entry Form", LABEL, 0, 0,2,0,"Last Name", LABEL, 0, 0,2,20,"First", LABEL, 0, 0,2,34,"Middle", LABEL, 0, 0,5,0,"Comments", STRING, 1, 18,3,0,(char *) 0, STRING, 1, 12,3,20,(char *) 0, STRING, 1, 12,3,34,(char *) 0, STRING, 4, 46,6,0,(char *) 0, (PF_field) 0, 0,0,0,0,(char *) 0, }; #define MAX_FIELD 512 static FIELD * fields [MAX_FIELD + 1]; /* field buffer */ static FIELD ** make_fields () /* create the fields */ { FIELD ** f = fields; int i; for (i = 0; i < MAX_FIELD && F[i].type; ++i, ++f) *f = (* F[i].type) (& F[i]); *f = (FIELD *) 0; return fields; } static void free_fields (f)/* free the fields */ FIELD ** f; { while (*f) free_field (*f++); }
Function main() first calls an application-defined routine make_fields() to create the fields and new_form() to create the form. Routine make_fields() offers a somewhat different way to create fields from what we have seen previously. (Array F holds the string labels and field sizes; it can be changed so that make_fields() can create any form.) Function main() then initializes curses using start_curses() and displays the form using display_form().
In its while loop, main() repeatedly calls form_driver() with the character returned by get_request(). If the form driver does not recognize the character as a request or data, it returns E_UNKNOWN_COMMAND, whereupon the application-defined routine my_driver() is called with the same character. Routine my_driver() processes the application-defined commands. In this example, there is only one, QUIT. Note how this request automatically calls the form driver again, now with the REQ_VALIDATION request. Remember that this request is necessary to ensure that current field validation occurs before your end-user leaves the form. If validation is successful, my_driver() returns TRUE. In turn, this sets done to TRUE, and the while loop is exited.
Finally, main() erases the form, terminates low-level ETI (curses), frees the form and its fields, and exits the program.
This example is typical, but it is only one of many ways you can structure an application. ETI's flexibility lets you use it over a wide range of applications.
Like other ETI routines that return an int, the form driver returns E_OK if it recognizes and processes the input character argument. If it encounters an error, it returns one of the following:
E_SYSTEM_ERROR -system error E_BAD_ARGUMEN -NULL form pointer E_BAD_STATE -called from init/term routines E_NOT_POSTED -form is not posted E_UNKNOWN_COMMAND -unknown command E_REQUEST_DENIED -recognized request failed E_INVALID_FIELD -failed field validation
NOTE: Like the menu driver, the form driver may not be called from any of the initialization or termination routines described next. Any attempt to do so returns E_BAD_STATE.
As with the menu driver, you may sometimes want the form driver to execute a specific routine whenever the current field or form changes. The following routines let you do this.
SYNOPSIS typedef void (* PTF_void ) (); int set_form_init (form, func) FORM * form; PTF_void func PTF_void form_init (form) FORM * form; int set_form_term (form, func) FORM * form; PTF_void func; PTF_void form_term (form) FORM * form; int set_field_init (form, func) FORM * form; PTF_void func PTF_void field_init (form) FORM * form; int set_field_term (form, func) FORM * form; PTF_void func; PTF_void field_term (form) FORM * form;
The argument func is a pointer to the specific function you want executed by the form driver. This application-defined function itself takes a form pointer as an argument.
As with menus, if you want your application to execute a routine at one of the initialization or termination points listed below, you should call the appropriate form initialization or termination routine at the start of your program. If you do not want a specific function called in these cases, you may refrain from calling these routines altogether.
The argument func to this function is automatically called by the form driver
The argument func to this function is automatically called by the form driver
The argument func to this function is automatically called by the form driver
The argument func to this function is automatically called by the form driver
To see more precisely when the initialization and termination routines may be executed, note that your form page and current field can be changed in the following circumstances:
NOTE: All of these initialization and termination functions are NULL by default. This means that no function need be called.
These functions promote common operations, such as row or column total updates, display of previously invisible fields, activation of previously inactive fields, and more. The following example shows a field termination routine update_total(), which dynamically adjusts a column total field whenever a row field value changes. Function main() calls set_field_term() to establish update_total() as the field termination routine.
Sample Termination Routine that Updates a Column Total
void update_total (form) FORM * form; { FIELD ** f = form_fields (form); char buf[80]; double total, atof(); /* atof() converts string to float */ switch (field_index (current_field (form))) { case ROW_1: case ROW_2: case ROW_3: /* field_buffer returns field's value as string, which atof converts to float */ total = atof (field_buffer (f[ROW_1], 0)) + /* calculate total */ atof (field_buffer (f[ROW_2], 0)) + atof (field_buffer (f[ROW_3], 0)); sprintf (buf, "%.2f", total); set_field_buffer (f[TOTAL], 0, buf); break; } } main () { FORM * form; set_field_term (form, update_total); /* establish termination routine */ }
Function set_field_buffer() sets the column total field to the value total stored in buf. See the earlier section "Setting and Reading Field Buffers" for details on field_buffer() and set_field_buffer().
For another example, the following shows a common use for field initialization and termination-highlighting a field when it becomes current and removing the highlight when it is no longer current.
Field Initialization and Termination to Highlight Current Field
void bold_off (form) FORM * form; { /* remove highlight */ set_field_back (current_field (form),A_UNDERLINE); } void bold_on (form) FORM * form; { /* highlight field */ set_field_back (current_field (form), A_STANDOUT | A_UNDERLINE); } main () { FORM * form; /* establish initialization and termination routines */ set_field_init (form, bold_on); set_field_term (form, bold_off); }
If functions set_form_init(), set_form_term(), set_field_init(), or set_field_term() encounter an error, they return the following:
E_SYSTEM_ERROR - system error
As usual, if you want a specific default initialization or termination function for all forms or all fields, you can pass the appropriate set function a NULL form pointer. Passing a NULL form pointer to the access functions returns the current ETI default.
The current field is the field where your end-user is positioned on the display screen. It changes as the end-user moves about the form entering or changing data. The cursor rests on the current field. To have your application program set or determine the current field, you use the following functions.
SYNOPSIS int set_current_field (form, field) FORM * form; FIELD * field; FIELD * current_field (form) FORM * form; int field_index (field) FIELD * field;
The function set_current_field() enables you to set the current field,while function current_field() returns the pointer to it. The value returned by field_index() is the index to the given field in the field pointer array associated with the connected form. This value is in the range of 0 through N-1, where N is the total number of fields.When a form is created by new_form() or the fields associated with the form are changed by set_form_fields() the current field is automatically set to the first visible, active field on page 0.
NOTE: Your application program need not call set_current_field unless you want to implement field navigation requests that are not supported by the form driver and discussed in the earlier section "ETI Form Requests.
The following illustrates the use of these functions. Function set_first_field() uses set_current_field() to set the current field to the first field in the form's field pointer array. Function first_field(), on the other hand, returns a Boolean value indicating whether the current field is the first field.
Example Manipulating the Current Field
int set_first_field (form) /* set current field to first field */ FORM * form; { FIELD ** f = form_fields (form); return set_current_field (form, f[0]); } int first_field (form) /* check if current field is first field */ FORM * form; { FIELD * f = current_field (form); return field_index (f) == 0; }
If function set_current_field() encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer or field not connected to form E_BAD_STATE - called from init/term routines E_INVALID_FIELD - current field is invalid on posted form E_REQUEST_DENIED - field not active or not visible
The function current_field() returns (FIELD *) 0 if given a NULL form pointer or there are no fields connected to the form.
The function field_index() returns -1 if its field pointer argument is NULL or the field is not connected to a form.
Two form functions enable your application program to change to another page on the form or to determine the current page of the form.
SYNOPSIS int set_form_page (form, page) FORM * form; int page; int form_page (form) FORM * form;
Upon execution of set_form_page,() the current field is set to the first field on the new page that is visible and active (visited during form driver processing). Variable page must be in the range of 0 through N-1, where N is the total number of pages. The function form_page() returns the page number of the page currently visible on the screen.
When function new_form() creates a form or function set_form_fields() changes the fields associated with a form,the form page is automatically set to 0.
NOTE: Your application program need not call set_form_page unless you want to implement page navigation requests that are not supported by the form driver and discussed in the earlier section "ETI Form Requests"
The following illustrates the use of these functions. Function set_first_page() uses set_form_page() to change to the first page of the form, while function first_page() uses form_page() to return a Boolean value indicating whether the first page of the form is currently displayed. Note that the first page is numbered 0.
Example Changing and Checking the Form Page Number
int set_first_page (form) /* set to first form page */ FORM * form; { return set_form_page (form, 0); } int first_page (form) /* check if on the first form page */ FORM * form; { return form_page (form) == 0; /* return Boolean */ }
If function set_form_page encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form or page out of range E_BAD_STATE - called from init/term routines E_INVALID_FIELD - current field is invalid on postedform
The function form_page() returns -1 if given a NULL form pointer or there are no fields connected to the form.
As with menu processing, some processing of user form requests may move the cursor from the location required for continued processing by the form driver. This function moves the cursor back to where it belongs.
SYNOPSIS int pos_form_cursor (form) FORM * form;
You need call this function only if your application program changes the cursor position of the form window.
The following screen illustrates one use of this function. Function printpage() repositions the cursor after it prints the page number in the form window.
Repositioning the Cursor After Printing Page Number
void printpage (form) FORM * form; { int p = form_page (form) + 1; WINDOW * w = form_win (form); int rows, cols; char buf[80]; box (w, 0, 0); /* put border around form window */ getmaxyx (w, rows, cols); /* fetch window size */ sprintf (buf, " %d ", p); /* store next page number */ wmove (w, (rows-1), ((cols-1)-strlen(buf))/2); /* position cursor */ waddstr (w, buf); /* print page number */ /* position the form cursor for continued form processing */ pos_form_cursor (form); } main () { FORM * form; set_form_init (form, printpage); }
If pos_form_cursor() encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL form pointer E_NOT_POSTED - form is not posted
As it does for items, menus, and fields, ETI supplies a form user pointer for data such as titles, help messages, and the like. These functions enable you to set the pointer and return its referent.
SYNOPSIS int set_form_userptr (form, userptr) FORM * form; char *userptr; char * form_userptr (form) FORM * form;
You can define a structure to be connected to the form using this pointer. By default, the form user pointer is NULL.
The following example illustrates the use of these form user pointer functions to determine whether a given name matches a pattern name. Function main() uses set_form_userptr() to establish the pattern name, while compare() uses form_userptr() to fetch the pattern and do the comparison.
Pattern Match Example Using form User Pointer
#define match(a,b) (strcmp (a, b) == 0) int compare (form, name) FORM * form; char * name; { char * s = form_userptr (form); /* fetch pattern string */ return match (name, s); /* return Boolean indicating match or not */ } main () { FORM * form; char * form_name; /* initialize form_name to desired string */ set_form_userptr (form, form_name); /* set user pointer to point to string */ }
For more user pointer examples, see the previous sections on item, menu, and field user pointers and the sample programs at the end of this guide.
If successful, set_form_userptr() returns E_OK. If not, it returns the following:
E_SYSTEM_ERROR - system error
As usual, you change the default by passing set_form_userptr() a NULL form pointer. So to change the default user pointer to point to the string ***, you write:
/* change default user pointer */ set_form_userptr((form*) 0, "***");
ETI provides form options regulating how specific user requests are handled. These functions enable you to set the options and read their settings.
SYNOPSIS int set_form_opts (form, opts) FORM * form; OPTIONS opts; OPTIONS form_opts (form) FORM * form; options: O_NL_OVERLOAD O_BS_OVERLOAD
Note that function set_form_opts() automatically turns off all form options not referenced in its second argument. By default, all options are on.
The effects of the options are as follows:
O_NL_OVERLOAD
determines how a REQ_NEW_LINE request is processed. If O_NL_OVERLOAD is on, the request is overloaded. See the earlier section "Field Editing Requests'' for a description of overloading. If O_NL_OVERLOAD is off, the REQ_NEW_LINE request behavior depends on whether insert mode is on.
In insert mode, the REQ_NEW_LINE request first inserts a new line after the current line. It then moves the text on the current line starting at the cursor position to the beginning of the new line. The cursor is repositioned to the beginning of the new line.
In overlay mode, the REQ_NEW_LINE request erases all data from the cursor position to the end of the line. It then repositions the cursor at the beginning of the next line.
If the field option O_STATIC if off and there is no maximum growth specified for the field, the overloaded form driver request REQ_NEW_LINE will operate the same way regardless of the setting of the O_NL_OVERLOAD form option. If a field can grow without bound, there is no last line, so REQ_NEW_LINE will never implicitly generate a REQ_NEXT_FIELD. If a maximum growth limit is specified and the O_NL_OVERLOAD form option is on, REQ_NEW_LINE will only implicitly generate REQ_NEXT_FIELD if the field has grown to its maximum size and the user is on the last line.
O_BS_OVERLOAD
determines how a REQ_DEL_PREV request is processed. If O_BS_OVERLOAD is on, the request is overloaded. See again the earlier section "Field Editing Requests'' for information on overloading. If O_BS_OVERLOAD is off, the REQ_DEL_PREV request depends on whether insert mode is on.
In insert mode, if the cursor is at the beginning of any line except the first and the text on the line will fit at the end of the previous line, the text is appended to the previous line and the current line is deleted. If not, the REQ_DEL_PREV request simply deletes the previous character, if there is one. If the cursor is at the first character of the field, the form driver simply returns E_REQUEST_DENIED.
In overlay mode, the REQ_DEL_PREV request simply deletes the previous character, if there is one.
Options are Boolean values, so you use Boolean operators to turn them on or off. For example, to turn off option O_NL_OVERLOAD of form f0 and turn on the same option of form f1, you write:
FORM * f0, * f1; set_form_opts (f0, form_opts (f0) & ~O_NL_OVERLOAD); /* turn option off */ set_form_opts (f1, form_opts (f1) | O_NL_OVERLOAD); /* turn option on */
ETI provides two more functions to turn options on and off.
SYNOPSIS int form_opts_on (form, opts) FORM * form; OPTIONS opts; int form_opts_off (form, opts) FORM * form; OPTIONS opts;
Unlike function set_form_opts(), these functions do not affect options unreferenced in their second argument.
Another way to turn off option O_NL_OVERLOAD on form f0 and turn it on on form f1 is to write:
FORM * f0, * f1; form_opts_off (f0, O_NL_OVERLOAD); /* turn option off */ form_opts_on (f1, O_NL_OVERLOAD); /* turn option on */
If functions set_form_opts(), form_opts_off(), or form_opts_on() encounter an error, they return the following:
E_SYSTEM_ERROR -system error
To change the current system default from, say, O_NL_OVERLOAD to not-O_NL_OVERLOAD without affecting the O_BS_OVERLOAD option, you write:
form_opts_off( (FORM *) 0, O_NL_OVERLOAD);
In addition to the wealth of field types that ETI automatically provides, ETI lets you create new field types from old ones. For most applications, you may not need them, but when you do, you will have them.
One way to define a new field type is to create one from two existing field types. The function link_fieldtype() lets you do this.
SYNOPSIS FIELDTYPE * link_fieldtype(type1,type2) FIELDTYPE * type1; FIELDTYPE * type2;
The constituent types may be system-defined or programmer-defined types. They may require additional arguments for the later call to set_field_type() and may be associated with validation functions or choice functions. Validation functions validate the value in the field, while choice functions enable the user to choose the next or previous value of the field type. See the sections "Creating a Field Type with Validation Functions" and "Supporting Next and Previous Choice Functions".
If additional arguments are required for the later call to set_field_type, those of type1 should precede those of type2. If there are validation or choice functions associated with the constituent types, the new type first executes the function associated with type1. If it is successful, it returns TRUE. If not, the new type executes the function associated with type2. Whatever it returns is the value returned by the new type.
As an example, the following code creates a new field type that accepts either a color keyword or an integer between 0 and 255, inclusive:
FIELD *f1; extern char ** colors; ENUM_OR_INT = link_fieldtype (TYPE_ENUM, TYPE_INTEGER); /* Constituent types are System types described in "Setting the Field Type to Ensure Validation" */ set_field_type (f1, ENUM_OR_INT, colors, FALSE, FALSE, 0, 0L, 255L); /* create field of field type ENUM_OR_INT */
Once you have created the new field type, you can create fields of that type. The last statement here creates field f1, which accepts only values of type ENUM_OR_INT.
If an error occurs, link_fieldtype() returns the following:
NULL -no available memory
Another way to create a new field type is by specifying
or both. Function new_fieldtype() returns your new field type given pointers to these validation functions.
SYNOPSIS typedef int (* PTF_int) (); FIELDTYPE * new_fieldtype (f_check, c_check) PTF_int f_check; PTF_int c_check;
The form driver automatically calls the named validation functions during form driver processing.
To create a new field type, you must write at least one of the two validation functions. Function f_check is a pointer to a function that takes two arguments: a field pointer and an argument pointer. The argument pointer is treated in the next section. f_check is called whenever the end-user tries to leave the field. It should check the field value stored in field buffer 0 and return TRUE if the field is valid or FALSE if not. If the validation function fails, your end-user remains on the offending field.
Function c_check is also a pointer to a function that takes two arguments: an integer that represents an ASCII character and an argument pointer. Function c_check is called as each character is entered by your end-user. It should check the character for validity and return TRUE if it is and FALSE if not.
Function new_fieldtype() is useful for creating field types for specialized applications. For example, the following defines a new field type TYPE_HEX as a hex number between 0x0000 and 0xffff.
Creating a Programmer-Defined Field Type
#include <ctype.h> #include <form.h> extern long strtol (); #define isblank(c) ((c) == ' ') static int padding = 4; /* pad on left to 4 digits */ static long vmin = 0x0000L; /* minimum acceptable value */ static long vmax = 0xffffL; /* maximum acceptable value */ static int fcheck_hex (f, arg) FIELD * f; char * arg; /* unnecessary here, discussed in the next section */ { char buf[80]; char * x = field_buffer (f, 0); while (*x && isblank (*x)) ++x; if (*x) { char * t = x; while (*x && isxdigit (*x)) ++x; while (*x && isblank (*x)) ++x; if (! *x) { long v = strtol (t, (char **) 0, 16); if (v >= vmin && v <= vmax) { sprintf (buf, "%.*lx", padding, v); set_field_buffer (f, 0, buf); return TRUE; } } } return FALSE; } static int ccheck_hex (c, arg) int c; char * arg; /* unnecessary in this example, discussed in the next section */ { return isxdigit (c); } FIELDTYPE * TYPE_HEX = new_fieldtype (fcheck_hex,ccheck_hex); /* create new field type */
Later, you assign fields with the field type TYPE_HEX as you do with any field type and field:
FIELD * field; set_field_type (field, TYPE_HEX);
Function ccheck_hex() checks that the input character is a valid hexadecimal digit, while function fcheck_hex() examines the field value for valid characters and checks the range. If successful, fcheck_hex() pads the field to four digits and returns TRUE. If not, it returns FALSE.
NOTE: The argument arg to functions f_check and c_check is not used in this version of the TYPE_HEX example because the new type does not require additional arguments to the set_field_type() routine.
If successful, new_fieldtype() returns a pointer to the new field type. If either argument to new_fieldtype() is a NULL pointer, the corresponding validation is not performed. If no memory is available or both function pointers are NULL, new_fieldtype() returns NULL.
This function frees any space allocated for a field type created with new_fieldtype() or link_fieldtype(). Its argument is a field type pointer previously obtained from one of these functions.
SYNOPSIS int free_fieldtype (fieldtype) FIELDTYPE * fieldtype;
You may want to free the field type TYPE_HEX from the previous example once fields of that type have been processed. To do so, you write
/* create field type TYPE_HEX */ create fields of this type free fields of this type */ free_fieldtype(TYPE_HEX); /* free programmer-defined type */
If successful, function free_fieldtype() returns E_OK. If an error occurs, it returns one of the following:
Once a field type is freed, you must not use it again. If you do, the effect is undefined.
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - NULL field type E_CONNECTED - type is connected to one or more fields
You may want to support some programmer-defined field types with additional arguments or with previous and next choice functions. This section explains how to do so.
Some field types may require additional arguments to the set_field_type() routine, which sets the field type of afield. Function set_fieldtype_arg() takes as arguments pointers to functions that manage storage for the additional arguments.
SYNOPSIS typedef char * (* PTF_charP) (); typedef void (* PTF_void) (); int set_fieldtype_arg (fieldtype, make_arg, copy_arg, free_arg) FIELDTYPE * fieldtype; PTF_charP make_arg; PTF_charP copy_arg; PTF_void free_arg;
You must write the functions referenced by pointers make_arg, copy_arg, and free_arg. These functions should do the following:
make_arg
allocate a structure for the field specific parameters to set_field_type() and return a pointer to the saved data
copy_arg
duplicate the structure created by make_arg
free_arg
free any storage allocated by make_arg or copy_arg
Function make_arg is called automatically when your application program calls set_field_type(). It takes one argument, a va_list *. (See varargs(5) for details.) Function make_arg in turn should call va_arg() for each additional argument to set_field_type() associated with the field type. Note that function va_start() is called by set_field_type() before make_arg gains control, while function va_end() is called by set_field_type() after make_arg returns.
Function make_arg must allocate space for the information associated with the additional arguments, save the information, and return the pointer to the information cast to a character pointer. It is this character pointer that is the argument arg to the other functions associated with the field type, namely copy_arg, free_arg, f_check,c_check, next_choice, and prev_choice.
Function copy_arg takes as its sole argument a pointer to existing argument information. It returns a pointer to a copy of this information. Function free_arg() takes as its sole argument a pointer to existing argument information. It should free any space allocated by make_arg.
The following example illustrates how you can add padding and range arguments to our TYPE_HEX defined above.
Creating TYPE_HEX with Padding and Range Arguments
/* TYPE_HEX set_field_type (f, TYPE_HEX, padding, vmin, vmax); int padding; for padding with leading zeros long vmin; minimum acceptable value long vmax; maximum acceptable value */ #include <form.h> #include <ctype.h> #include <varargs.h> extern long strtol (); #define isblank(c) ((c) == ' ') typedef struct { int padding; long vmin, vmax; } HEX; static char * make_hex (ap) va_list * ap; { HEX * n = (HEX *) malloc (sizeof (HEX)); if (n) { n -> padding = va_arg (*ap, int); n -> vmin = va_arg (*ap, long); n -> vmax = va_arg (*ap, long); } return (char *) n; } static char * copy_hex (arg) char * arg; { HEX * n = (HEX *) malloc (sizeof (HEX)); if (n) *n = *((HEX *) arg); return (char *) n; } static void free_hex (arg) char * arg; { free (arg); } static int fcheck_hex (f, arg) FIELD * f; char * arg; { HEX * n = (HEX *) arg; int padding = n -> padding; long vmin = n -> vmin; long vmax = n -> vmax; char buf[80]; char * x = field_buffer (f, 0); while (*x && isblank (*x)) ++x; if (*x) { char * t = x; while (*x && isxdigit (*x)) ++x; while (*x && isblank (*x)) ++x; if (! *x) { long v = strtol (t, (char **) 0, 16); if (v >= vmin && v <= vmax) { sprintf (buf, "%.*lx", padding, v); set_field_buffer (f, 0, buf); return TRUE; } } } return FALSE; } static int ccheck_hex (c, arg) int c; char * arg; { return isxdigit (c); } FIELDTYPE * TYPE_HEX = new_fieldtype (fcheck_hex, ccheck_hex); set_fieldtype_arg (TYPE_HEX, make_hex, copy_hex, free_hex);
Later, to create a field that stores a hex number between 0x0000 and 0xffff, we have:
set_field_type (field, TYPE_HEX, 4, 0x0000L, 0xffffL);
From this example, note that
If successful, function set_fieldtype_arg returns E_OK. If an error occurs, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - field type, make_arg, copy_arg, or free_arg is NULL
Some field types comprise a set of values from which your user chooses (enters) one. The following functions support those types that have a set of choices.
SYNOPSIS typedef char * (* PTF_charP) () ;int set_fieldtype_choice (type, next_choice, prev_choice) FIELDTYPE * type; PTF_int next_choice; PTF_int prev_choice; int next_choice(f,arg); FIELD * f; char * arg; int prev_choice(f,arg); FIELD * f; char * arg;
These functions enable the ETI form driver to support the REQ_NEXT_CHOICE and REQ_PREV_CHOICE requests mentioned in the earlier section "Form Driver Processing''
To support these requests, your application-defined functions next_choice and prev_choice must
Both functions can be quite similar.
The following example shows an implementation of function next_choice() for the field type TYPE_HEX as defined above, such that REQ_NEXT_CHOICE increments the current value and REG_PREV_CHOICE decrements the current value.
Creating a Next Choice Function for a Field Type
static int next_hex (f, arg) FIELD * f; char * arg; { HEX * n = (HEX *) arg; long v = n -> vmin; char buf[80]; char * x = field_buffer (f, 0); while (*x && isblank (*x)) ++x; if (*x) { v = strtol (x, (char **) 0, 16); if (v >= n -> vmin && v < n -> vmax) ++v; } sprintf (buf, "%.*lx", n -> padding, v); set_field_buffer (f, 0, buf); return TRUE; } static int prev_hex (f, arg) FIELD * f; char * arg; { HEX * n = (HEX *) arg; long v = n -> vmax; char buf[80]; char * x = field_buffer (f, 0); while (*x && isblank (*x)) ++x; if (*x) { v = strtol (x, (char **) 0, 16); if (v > n -> vmin && v <= n -> vmax) --v; } sprintf (buf, "%.*lx", v -> padding, v); set_field_buffer (f, 0, buf); return TRUE; } /* associate previous and next choice functions */ set_fieldtype_choice (TYPE_HEX, next_hex, prev_hex);
If given a blank field, your functions next_choice and prev_choice should, of course, do something reasonable,such as setting the field to the first or last value of the type.
If function set_fieldtype_choice() encounters an error, it returns one of the following:
E_SYSTEM_ERROR - system error E_BAD_ARGUMENT - field type, next_choice, or prev_choice is NULL