Previous Page TOC Next Page

Character User Interface (FMLI and ETI)

Forms

Introduction


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

Compiling and Linking Form Programs


To use the form routines, you specify


in your C program files and compile and link with the command line


If you want to use the menu or panel routines as well, place the appropriate -l option before the option -lcurses.

Overview: Writing Form Programs in ETI


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.

Some Important Form Terminology


The following terms are helpful in working with ETI form functions:


field
an m x n block of form character positions that ETI functions can manipulate as a unit


active field
a field that is visited during form processing for data entry, change, selection, and so forth


inactive field
a field that is completely ignored during form processing, such as a title, field marker or other label


dynamic field
a field whose buffer grows beyond its original size if more data is entered into the field than the original buffer will hold.


form
a collection of one or more pages of fields


connecting fields to a form
associating an array of field pointers with a form


page
a logical subdivision of a form usually occupying one screen


posting a form
writing a form on its associated subwindow


unposting a form
erasing a form from its associated subwindow


freeing a form
deallocating the memory for a form and, as a by- product, disconnecting the previously associated array of field pointers from the form


freeing a field
deallocating the memory for a field


NULL
generic term for a null pointer cast to the type of the particular object (field, form, and so on)

What a Typical Form Application Program Does


In general, a form application program will

A Sample Form Application Program


The following example shows the ETI program necessary for producing a form.

Code to Produce a Simple Form


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.

Creating and Freeing Fields


To create a form, you must first create its fields. The following functions enable you to create fields and later free them.


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:


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:


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:


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:


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.

Manipulating Field Attributes


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.

Obtaining Field Size and Location Information


This function enables you to determine the defining characteristics of a field - its size, position, number of offscreen rows, and number of associated buffers.


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:


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:

Dynamically Growable Fields


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.


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:


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.


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:


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.

Moving a Field


ETI provides the following function to move an existing disconnected field to a new location.


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


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:

Changing the Current Default Values for Field Attributes


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.

Setting the Field Type to Ensure Validation


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.


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:


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


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.

TYPE_ALPHA


The form driver restricts a field of this type to alphabetic data.


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

TYPE_ALNUM


This type restricts the set field to alphanumeric data, alphabetic characters (upper- or lower-case) and digits.


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

TYPE_ENUM


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.


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:


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


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.

TYPE_INTEGER


This type enables you to restrict the data in a field to integers.


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


whereas a precision of 4 would display


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:


Note the requirement that the minimum and maximum values be converted to type long with the L.

TYPE_NUMERIC


This type restricts the data for the set field to decimal numbers.


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:

TYPE_REGEXP


This type enables you to determine whether the data entered into a field matches a specific 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:


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:

Justifying Data in a Field


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.


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 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:


If successful, set_field_just returns E_OK. If not, it returns one of the following:


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

Setting the Field Foreground, Background, and Pad Character


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


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:


If function set_field_fore or set_field_back encounter an error, they return one of the following:


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:


If successful, function set_field_pad returns E_OK. If not, it returns one of the following:


As usual, you can change or access the ETI defaults. To change the default background to A_UNDERLINE, you write:

Some Helpful Features of Fields


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.

Setting and Reading Field Buffers


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.


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.


If successful, set_field_buffer returns E_OK. If not, it returns one of the following: e


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.

Setting and Reading the Field Status


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.


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


If successful, set_field_status() returns E_OK. If not, it returns the following:


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.

Setting and Fetching the Field User Pointer


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


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


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:


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.

Manipulating Field Options


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.


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:


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().


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:


If successful, functions set_field_opts(), field_opts_on(), and field_opts_off() return E_OK. If not, they return the following:


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.

Creating and Freeing Forms


Once you have established a set of fields and their attributes, you are ready to create a form to contain them.


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.


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:


The following example shows how to create a simple two-page form.

Creating a 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:


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.

Manipulating Form Attributes


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.

Changing and Fetching the Fields on an Existing Form


Once you create a form with one set of fields using new_form, you can change the fields connected to it.


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:


If function set_form_fields() encounters an error, it returns one of the following:


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.

Counting the Number of Fields


The following function returns the number of fields connected to the given 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:


Note the use of functions field_index() and current_field(), described below in the section "Manipulating the Current Field"

Querying the Presence of Offscreen Data


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.


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.

Changing ETI Form Default Attributes


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.

Displaying Forms


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.

Determining the Dimensions of Forms


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.

Scaling the Form


Considering the above information, this function returns the minimum window size necessary for containing the form.


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:


If function scale_form() encounters an error, it returns one of the following:

Associating Windows and Subwindows with a 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.


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.

Undisplayed Graphic

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


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:


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:


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.

Posting and Unposting Forms


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.


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


If successful, function post_form() returns E_OK. If not, it returns one of the following:


If successful, the function unpost_form() returns E_OK. If not, it returns one of the following:


The initialization and termination routines are discussed in the next section.

Form Driver Processing


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.


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.

Defining the Virtual Key Mapping


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


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).

ETI Form Requests


The ETI form subsystem places the following requests at your application program's disposal.

Page Navigation Requests


These requests enable your end-user to navigate or move from page to page on a multi-page form.


Page navigation requests are cyclic so that

Inter-field Navigation Requests on the Current Page


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


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.

Intra-field Navigation Requests


These requests let your end-user move about inside a field. They may generate implicit scrolling operations on scrollable fields.


The effect of these requests is as follows:

Field Editing Requests


These requests set the editing mode - insert or overlay.


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.


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.

Scrolling Requests


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.


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.


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.

Field Validation Requests


This request supports field validation for those field types that have it.


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.

Choice Requests


The following requests enable your user to request the next or previous value of a field type.


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.

Application-defined Commands


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.

Calling the Form Driver


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.

Undisplayed Graphic

You have already seen much of the sweepstakes program in previous examples. The following shows its remaining routines.

An Example of Form Driver Usage


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:


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.

Establishing Field and Form Initialization and Termination Routines


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.


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.

Function set_form_init()


The argument func to this function is automatically called by the form driver

Function set_field_init()


The argument func to this function is automatically called by the form driver

Function set_field_term()


The argument func to this function is automatically called by the form driver

Function set_form_term()


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


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


If functions set_form_init(), set_form_term(), set_field_init(), or set_field_term() encounter an error, they return the following:


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.

Manipulating the Current Field


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.


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


If function set_current_field() encounters an error, it returns one of the following:


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.

Changing the Form Page


Two form functions enable your application program to change to another page on the form or to determine the current page of the 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


If function set_form_page encounters an error, it returns one of the following:


The function form_page() returns -1 if given a NULL form pointer or there are no fields connected to the form.

Positioning the Form Cursor


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.


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


If pos_form_cursor() encounters an error, it returns one of the following:

Setting and Fetching the Form User Pointer


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.


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


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:


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:

Setting and Fetching Form Options


ETI provides form options regulating how specific user requests are handled. These functions enable you to set the options and read their settings.


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:


ETI provides two more functions to turn options on and off.


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:


If functions set_form_opts(), form_opts_off(), or form_opts_on() encounter an error, they return the following:


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:

Creating and Manipulating Programmer-defined Field Types


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.

Building a Field Type from Two Other Field Types


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.


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:


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:

Creating a Field Type with Validation Functions


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.


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


Later, you assign fields with the field type TYPE_HEX as you do with any field type and field:


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.

Freeing Programmer-defined Field Types


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.


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


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.

Supporting Programmer-defined Field Types


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.

Argument Support for Field Types


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.


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


Later, to create a field that stores a hex number between 0x0000 and 0xffff, we have:


From this example, note that


If successful, function set_fieldtype_arg returns E_OK. If an error occurs, it returns one of the following:

Supporting Next and Previous Choice Functions


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.


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


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: