18. Forms Library

Well. If you have seen those forms on web pages which take input from users and do various kinds of things, you might be wondering how would any one create such forms in text mode display. It's quite difficult to write those nifty forms in plain ncurses. Forms library tries to provide a basic frame work to build and maintain forms with ease. It has lot of features(functions) which manage validation, dynamic expansion of fields etc.. Let's see it in full flow.

A form is a collection of fields; each field can be either a label(static text) or a data-entry location. The forms also library provides functions to divide forms into multiple pages.

18.1. The Basics

Forms are created in much the same way as menus. First the fields related to the form are created with new_field(). You can set options for the fields, so that they can be displayed with some fancy attributes, validated before the field looses focus etc.. Then the fields are attached to form. After this, the form can be posted to display and is ready to receive inputs. On the similar lines to menu_driver(), the form is manipulated with form_driver(). We can send requests to form_driver to move focus to a certain field, move cursor to end of the field etc.. After the user enters values in the fields and validation done, form can be unposted and memory allocated can be freed.

The general flow of control of a forms program looks like this.

  1. Initialize curses

  2. Create fields using new_field(). You can specify the height and width of the field, and its position on the form.

  3. Create the forms with new_form() by specifying the fields to be attached with.

  4. Post the form with form_post() and refresh the screen.

  5. Process the user requests with a loop and do necessary updates to form with form_driver.

  6. Unpost the menu with form_unpost()

  7. Free the memory allocated to menu by free_form()

  8. Free the memory allocated to the items with free_field()

  9. End curses

As you can see, working with forms library is much similar to handling menu library. The following examples will explore various aspects of form processing. Let's start the journey with a simple example. first.

18.2. Compiling With the Forms Library

To use forms library functions, you have to include form.h and to link the program with forms library the flag -lform should be added along with -lncurses in that order.

    #include <form.h>
    .
    .
    .

    compile and link: gcc <program file> -lform -lncurses

Example 25. Forms Basics

#include <form.h>

int main()
{	FIELD *field[3];
	FORM  *my_form;
	int ch;
	
	/* Initialize curses */
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);

	/* Initialize the fields */
	field[0] = new_field(1, 10, 4, 18, 0, 0);
	field[1] = new_field(1, 10, 6, 18, 0, 0);
	field[2] = NULL;

	/* Set field options */
	set_field_back(field[0], A_UNDERLINE); 	/* Print a line for the option 	*/
	field_opts_off(field[0], O_AUTOSKIP);  	/* Don't go to next field when this */
						/* Field is filled up 		*/
	set_field_back(field[1], A_UNDERLINE); 
	field_opts_off(field[1], O_AUTOSKIP);

	/* Create the form and post it */
	my_form = new_form(field);
	post_form(my_form);
	refresh();
	
	mvprintw(4, 10, "Value 1:");
	mvprintw(6, 10, "Value 2:");
	refresh();

	/* Loop through to get user requests */
	while((ch = getch()) != KEY_F(1))
	{	switch(ch)
		{	case KEY_DOWN:
				/* Go to next field */
				form_driver(my_form, REQ_NEXT_FIELD);
				/* Go to the end of the present buffer */
				/* Leaves nicely at the last character */
				form_driver(my_form, REQ_END_LINE);
				break;
			case KEY_UP:
				/* Go to previous field */
				form_driver(my_form, REQ_PREV_FIELD);
				form_driver(my_form, REQ_END_LINE);
				break;
			default:
				/* If this is a normal character, it gets */
				/* Printed				  */	
				form_driver(my_form, ch);
				break;
		}
	}

	/* Un post form and free the memory */
	unpost_form(my_form);
	free_form(my_form);
	free_field(field[0]);
	free_field(field[1]); 

	endwin();
	return 0;
}

Above example is pretty straight forward. It creates two fields with new_field(). new_field() takes height, width, starty, startx, number of offscreen rows and number of additional working buffers. The fifth argument number of offscreen rows specifies how much of the field to be shown. If it is zero, the entire field is always displayed otherwise the form will be scrollable when the user accesses not displayed parts of the field. The forms library allocates one buffer per field to store the data user enters. Using the last parameter to new_field() we can specify it to allocate some additional buffers. These can be used for any purpose you like.

After creating the fields, back ground attribute of both of them is set to an underscore with set_field_back(). The AUTOSKIP option is turned off using field_opts_off(). If this option is turned on, focus will move to the next field in the form once the active field is filled up completely.

After attaching the fields to the form, it is posted. Here on, user inputs are processed in the while loop, by making corresponding requests to form_driver. The details of all the requests to the form_driver() are explained later.

18.3. Playing with Fields

Each form field is associated with a lot of attributes. They can be manipulated to get the required effect and to have fun !!!. So why wait?

18.3.1. Fetching Size and Location of Field

The parameters we have given at the time of creation of a field can be retrieved with field_info(). It returns height, width, starty, startx, number of offscreen rows, and number of additional buffers into the parameters given to it. It is a sort of inverse of new_field().

int field_info(     FIELD *field,              /* field from which to fetch */
                    int *height, *int width,   /* field size */ 
                    int *top, int *left,       /* upper left corner */
                    int *offscreen,            /* number of offscreen rows */
                    int *nbuf);                /* number of working buffers */

18.3.2. Moving the field

The location of the field can be moved to a different position with move_field().

int move_field(    FIELD *field,              /* field to alter */
                   int top, int left);        /* new upper-left corner */

As usual, the changed position can be queried with field_infor().

18.3.3. Field Justification

The justification to be done for the field can be fixed using the function set_field_just().

    int set_field_just(FIELD *field,          /* field to alter */
               int justmode);         /* mode to set */
    int field_just(FIELD *field);          /* fetch justify mode of field */

The justification mode valued accepted and returned by these functions are NO_JUSTIFICATION, JUSTIFY_RIGHT, JUSTIFY_LEFT, or JUSTIFY_CENTER.

18.3.4. Field Display Attributes

As you have seen, in the above example, display attribute for the fields can be set with set_field_fore() and setfield_back(). These functions set foreground and background attribute of the fields. You can also specify a pad character which will be filled in the unfilled portion of the field. The pad character is set with a call to set_field_pad(). Default pad value is a space. The functions field_fore(), field_back, field_pad() can be used to query the present foreground, background attributes and pad character for the field. The following list gives the usage of functions.


int set_field_fore(FIELD *field,        /* field to alter */
                   chtype attr);        /* attribute to set */ 

chtype field_fore(FIELD *field);        /* field to query */
                                        /* returns foreground attribute */

int set_field_back(FIELD *field,        /* field to alter */
                   chtype attr);        /* attribute to set */ 

chtype field_back(FIELD *field);        /* field to query */
                                        /* returns background attribute */

int set_field_pad(FIELD *field,         /* field to alter */
                  int pad);             /* pad character to set */ 

chtype field_pad(FIELD *field);         /* field to query */  
                                        /* returns present pad character */

Though above functions seem quite simple, using colors with set_field_fore() may be frustrating in the beginning. Let me first explain about foreground and background attributes of a field. The foreground attribute is associated with the character. That means a character in the field is printed with the attribute you have set with set_field_fore(). Background attribute is the attribute used to fill background of field, whether any character is there or not. So what about colors? Since colors are always defined in pairs, what is the right way to display colored fields? Here's an example clarifying color attributes.

Example 26. Form Attributes example

#include <form.h>

int main()
{	FIELD *field[3];
	FORM  *my_form;
	int ch;
	
	/* Initialize curses */
	initscr();
	start_color();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);

	/* Initialize few color pairs */
	init_pair(1, COLOR_WHITE, COLOR_BLUE);
	init_pair(2, COLOR_WHITE, COLOR_BLUE);

	/* Initialize the fields */
	field[0] = new_field(1, 10, 4, 18, 0, 0);
	field[1] = new_field(1, 10, 6, 18, 0, 0);
	field[2] = NULL;

	/* Set field options */
	set_field_fore(field[0], COLOR_PAIR(1));/* Put the field with blue background */
	set_field_back(field[0], COLOR_PAIR(2));/* and white foreground (characters */
						/* are printed in white 	*/
	field_opts_off(field[0], O_AUTOSKIP);  	/* Don't go to next field when this */
						/* Field is filled up 		*/
	set_field_back(field[1], A_UNDERLINE); 
	field_opts_off(field[1], O_AUTOSKIP);

	/* Create the form and post it */
	my_form = new_form(field);
	post_form(my_form);
	refresh();
	
	set_current_field(my_form, field[0]); /* Set focus to the colored field */
	mvprintw(4, 10, "Value 1:");
	mvprintw(6, 10, "Value 2:");
	mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
	refresh();

	/* Loop through to get user requests */
	while((ch = getch()) != KEY_F(1))
	{	switch(ch)
		{	case KEY_DOWN:
				/* Go to next field */
				form_driver(my_form, REQ_NEXT_FIELD);
				/* Go to the end of the present buffer */
				/* Leaves nicely at the last character */
				form_driver(my_form, REQ_END_LINE);
				break;
			case KEY_UP:
				/* Go to previous field */
				form_driver(my_form, REQ_PREV_FIELD);
				form_driver(my_form, REQ_END_LINE);
				break;
			default:
				/* If this is a normal character, it gets */
				/* Printed				  */	
				form_driver(my_form, ch);
				break;
		}
	}

	/* Un post form and free the memory */
	unpost_form(my_form);
	free_form(my_form);
	free_field(field[0]);
	free_field(field[1]); 

	endwin();
	return 0;
}

Play with the color pairs and try to understand the foreground and background attributes. In my programs using color attributes, I usually set only the background with set_field_back(). Curses simply doesn't allow defining individual color attributes.

18.3.5. Field Option Bits

There is also a large collection of field option bits you can set to control various aspects of forms processing. You can manipulate them with these functions:

int set_field_opts(FIELD *field,          /* field to alter */
                   int attr);             /* attribute to set */ 

int field_opts_on(FIELD *field,           /* field to alter */
                  int attr);              /* attributes to turn on */ 

int field_opts_off(FIELD *field,          /* field to alter */
                  int attr);              /* attributes to turn off */ 

int field_opts(FIELD *field);             /* field to query */ 

The function set_field_opts() can be used to directly set attributes of a field or you can choose to switch a few attributes on and off with field_opts_on() and field_opts_off() selectively. Anytime you can query the attributes of a field with field_opts(). The following is the list of available options. By default, all options are on.

O_VISIBLE

Controls whether the field is visible on the screen. Can be used during form processing to hide or pop up fields depending on the value of parent fields.

O_ACTIVE

Controls whether the field is active during forms processing (i.e. visited by form navigation keys). Can be used to make labels or derived fields with buffer values alterable by the forms application, not the user.

O_PUBLIC

Controls whether data is displayed during field entry. If this option is turned off on a field, the library will accept and edit data in that field, but it will not be displayed and the visible field cursor will not move. You can turn off the O_PUBLIC bit to define password fields.

O_EDIT

Controls whether the field's data can be modified. When this option is off, all editing requests except REQ_PREV_CHOICE and REQ_NEXT_CHOICEwill fail. Such read-only fields may be useful for help messages.

O_WRAP

Controls word-wrapping in multi-line fields. Normally, when any character of a (blank-separated) word reaches the end of the current line, the entire word is wrapped to the next line (assuming there is one). When this option is off, the word will be split across the line break.

O_BLANK

Controls field blanking. When this option is on, entering a character at the first field position erases the entire field (except for the just-entered character).

O_AUTOSKIP

Controls automatic skip to next field when this one fills. Normally, when the forms user tries to type more data into a field than will fit, the editing location jumps to next field. When this option is off, the user's cursor will hang at the end of the field. This option is ignored in dynamic fields that have not reached their size limit.

O_NULLOK

Controls whether validation is applied to blank fields. Normally, it is not; the user can leave a field blank without invoking the usual validation check on exit. If this option is off on a field, exit from it will invoke a validation check.

O_PASSOK

Controls whether validation occurs on every exit, or only after the field is modified. Normally the latter is true. Setting O_PASSOK may be useful if your field's validation function may change during forms processing.

O_STATIC

Controls whether the field is fixed to its initial dimensions. If you turn this off, the field becomes dynamic and will stretch to fit entered data.

A field's options cannot be changed while the field is currently selected. However, options may be changed on posted fields that are not current.

The option values are bit-masks and can be composed with logical-or in the obvious way. You have seen the usage of switching off O_AUTOSKIP option. The following example clarifies usage of some more options. Other options are explained where appropriate.

Example 27. Field Options Usage example

#include <form.h>

#define STARTX 15
#define STARTY 4
#define WIDTH 25

#define N_FIELDS 3

int main()
{	FIELD *field[N_FIELDS];
	FORM  *my_form;
	int ch, i;
	
	/* Initialize curses */
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);

	/* Initialize the fields */
	for(i = 0; i < N_FIELDS - 1; ++i)
		field[i] = new_field(1, WIDTH, STARTY + i * 2, STARTX, 0, 0);
	field[N_FIELDS - 1] = NULL;

	/* Set field options */
	set_field_back(field[1], A_UNDERLINE); 	/* Print a line for the option 	*/
	
	field_opts_off(field[0], O_ACTIVE); /* This field is a static label */
	field_opts_off(field[1], O_PUBLIC); /* This filed is like a password field*/
	field_opts_off(field[1], O_AUTOSKIP); /* To avoid entering the same field */
					      /* after last character is entered */
	
	/* Create the form and post it */
	my_form = new_form(field);
	post_form(my_form);
	refresh();
	
	set_field_just(field[0], JUSTIFY_CENTER); /* Center Justification */
	set_field_buffer(field[0], 0, "This is a static Field"); 
						  /* Initialize the field  */
	mvprintw(STARTY, STARTX - 10, "Field 1:");
	mvprintw(STARTY + 2, STARTX - 10, "Field 2:");
	refresh();

	/* Loop through to get user requests */
	while((ch = getch()) != KEY_F(1))
	{	switch(ch)
		{	case KEY_DOWN:
				/* Go to next field */
				form_driver(my_form, REQ_NEXT_FIELD);
				/* Go to the end of the present buffer */
				/* Leaves nicely at the last character */
				form_driver(my_form, REQ_END_LINE);
				break;
			case KEY_UP:
				/* Go to previous field */
				form_driver(my_form, REQ_PREV_FIELD);
				form_driver(my_form, REQ_END_LINE);
				break;
			default:
				/* If this is a normal character, it gets */
				/* Printed				  */	
				form_driver(my_form, ch);
				break;
		}
	}

	/* Un post form and free the memory */
	unpost_form(my_form);
	free_form(my_form);
	free_field(field[0]);
	free_field(field[1]); 

	endwin();
	return 0;
}

This example, though useless, shows the usage of options. If used properly, they can present information very effectively in a form. The second field being not O_PUBLIC, does not show the characters you are typing.

18.3.6. Field Status

The field status specifies whether the field has got edited or not. It is initially set to FALSE and when user enters something and the data buffer gets modified it becomes TRUE. So a field's status can be queried to find out whether it has been modified or not. The following functions can assist in those operations.

int set_field_status(FIELD *field,      /* field to alter */
                   int status);         /* status to set */

int field_status(FIELD *field);         /* fetch status of field */

It's better to check the field's status only after after leaving the field, as data buffer might not have been updated yet as the validation is still due. To guarantee that right status is returned, call field_status() either (1) in the field's exit validation check routine, (2) from the field's or form's initialization or termination hooks, or (3) just after a REQ_VALIDATION request has been processed by the forms driver

18.3.7. Field User Pointer

Every field structure contains one pointer that can be used by the user for various purposes. It is not touched by forms library and can be used for any purpose by the user. The following functions set and fetch user pointer.

int set_field_userptr(FIELD *field,   
           char *userptr);      /* the user pointer you wish to associate */
                                /* with the field    */

char *field_userptr(FIELD *field);      /* fetch user pointer of the field */

18.3.8. Variable-Sized Fields

If you want a dynamically changing field with variable width, this is the feature you want to put to full use. This will allow the user to enter more data than the original size of the field and let the field grow. According to the field orientation it will scroll horizontally or vertically to incorporate the new data.

To make a field dynamically growable, the option O_STATIC should be turned off. This can be done with a
    field_opts_off(field_pointer, O_STATIC);

But it's usually not advisable to allow a field to grow infinitely. You can set a maximum limit to the growth of the field with
int set_max_field(FIELD *field,    /* Field on which to operate */
                  int max_growth); /* maximum growth allowed for the field */

The field info for a dynamically growable field can be retrieved by
int dynamic_field_info( FIELD *field,     /* Field on which to operate */
            int   *prows,     /* number of rows will be filled in this */
            int   *pcols,     /* number of columns will be filled in this*/
            int   *pmax)      /* maximum allowable growth will be filled */
                              /* in this */
Though field_info work as usual, it is advisable to use this function to get the proper attributes of a dynamically growable field.

Recall the library routine new_field; a new field created with height set to one will be defined to be a one line field. A new field created with height greater than one will be defined to be a multi line field.

A one line field with O_STATIC turned off (dynamically growable field) 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 multi line field with O_STATIC turned off (dynamically growable field) 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.

The above two paragraphs pretty much describe a dynamically growable field's behavior. The way other parts of forms library behaves is described below:

  1. The field option O_AUTOSKIP will be ignored if the option O_STATIC is 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 usual; 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 usual; 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 usual; 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.

Some of the above points make sense only after explaining form driver. We will be looking into that in next few sections.

18.4. Form Windows

The form windows concept is pretty much similar to menu windows. Every form is associated with a main window and a sub window. The form main window displays any title or border associated or whatever the user wishes. Then the sub window contains all the fields and displays them according to their position. This gives the flexibility of manipulating fancy form displaying very easily.

Since this is pretty much similar to menu windows, I am providing an example with out much explanation. The functions are similar and they work the same way.

Example 28. Form Windows Example

#include <form.h>

void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color);

int main()
{
	FIELD *field[3];
	FORM  *my_form;
	WINDOW *my_form_win;
	int ch, rows, cols;
	
	/* Initialize curses */
	initscr();
	start_color();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);

	/* Initialize few color pairs */
   	init_pair(1, COLOR_RED, COLOR_BLACK);

	/* Initialize the fields */
	field[0] = new_field(1, 10, 6, 1, 0, 0);
	field[1] = new_field(1, 10, 8, 1, 0, 0);
	field[2] = NULL;

	/* Set field options */
	set_field_back(field[0], A_UNDERLINE);
	field_opts_off(field[0], O_AUTOSKIP); /* Don't go to next field when this */
					      /* Field is filled up 		*/
	set_field_back(field[1], A_UNDERLINE); 
	field_opts_off(field[1], O_AUTOSKIP);
	
	/* Create the form and post it */
	my_form = new_form(field);
	
	/* Calculate the area required for the form */
	scale_form(my_form, &rows, &cols);

	/* Create the window to be associated with the form */
        my_form_win = newwin(rows + 4, cols + 4, 4, 4);
        keypad(my_form_win, TRUE);

	/* Set main window and sub window */
        set_form_win(my_form, my_form_win);
        set_form_sub(my_form, derwin(my_form_win, rows, cols, 2, 2));

	/* Print a border around the main window and print a title */
        box(my_form_win, 0, 0);
	print_in_middle(my_form_win, 1, 0, cols + 4, "My Form", COLOR_PAIR(1));
	
	post_form(my_form);
	wrefresh(my_form_win);

	mvprintw(LINES - 2, 0, "Use UP, DOWN arrow keys to switch between fields");
	refresh();

	/* Loop through to get user requests */
	while((ch = wgetch(my_form_win)) != KEY_F(1))
	{	switch(ch)
		{	case KEY_DOWN:
				/* Go to next field */
				form_driver(my_form, REQ_NEXT_FIELD);
				/* Go to the end of the present buffer */
				/* Leaves nicely at the last character */
				form_driver(my_form, REQ_END_LINE);
				break;
			case KEY_UP:
				/* Go to previous field */
				form_driver(my_form, REQ_PREV_FIELD);
				form_driver(my_form, REQ_END_LINE);
				break;
			default:
				/* If this is a normal character, it gets */
				/* Printed				  */	
				form_driver(my_form, ch);
				break;
		}
	}

	/* Un post form and free the memory */
	unpost_form(my_form);
	free_form(my_form);
	free_field(field[0]);
	free_field(field[1]); 

	endwin();
	return 0;
}

void print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color)
{	int length, x, y;
	float temp;

	if(win == NULL)
		win = stdscr;
	getyx(win, y, x);
	if(startx != 0)
		x = startx;
	if(starty != 0)
		y = starty;
	if(width == 0)
		width = 80;

	length = strlen(string);
	temp = (width - length)/ 2;
	x = startx + (int)temp;
	wattron(win, color);
	mvwprintw(win, y, x, "%s", string);
	wattroff(win, color);
	refresh();
}

18.5. Field Validation

By default, a field will accept any data input by the user. It is possible to attach validation to the field. Then any attempt by the user to leave the field, while it contains data that doesn't match the validation type will fail. Some validation types also have a character-validity check for each time a character is entered in the field.

Validation can be attached to a field with the following function.
int set_field_type(FIELD *field,          /* field to alter */
                   FIELDTYPE *ftype,      /* type to associate */
                   ...);                  /* additional arguments*/
Once set, the validation type for a field can be queried with
FIELDTYPE *field_type(FIELD *field);      /* field to query */

The form driver validates the data in a field only when data is entered by the end-user. Validation does not occur when

The following are the pre-defined validation types. You can also specify custom validation, though it's a bit tricky and cumbersome.

TYPE_ALPHA

This field type accepts alphabetic data; no blanks, no digits, no special characters (this is checked at character-entry time). It is set up with:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_ALPHA,            /* type to associate */
                   int width);            /* maximum width of field */

The width argument sets a minimum width of data. The user has to enter at-least width number of characters before he can leave the field. Typically you'll want to set this to the field width; if it's greater than the field width, the validation check will always fail. A minimum width of zero makes field completion optional.

TYPE_ALNUM

This field type accepts alphabetic data and digits; no blanks, no special characters (this is checked at character-entry time). It is set up with:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_ALNUM,            /* type to associate */
                   int width);            /* maximum width of field */

The width argument sets a minimum width of data. As with TYPE_ALPHA, typically you'll want to set this to the field width; if it's greater than the field width, the validation check will always fail. A minimum width of zero makes field completion optional.

TYPE_ENUM

This type allows you to restrict a field's values to be among a specified set of string values (for example, the two-letter postal codes for U.S. states). It is set up with:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_ENUM,             /* type to associate */
                   char **valuelist;      /* list of possible values */
                   int checkcase;         /* case-sensitive? */
                   int checkunique);      /* must specify uniquely? */

The valuelist parameter must point at a NULL-terminated list of valid strings. The checkcase argument, if true, makes comparison with the string case-sensitive.

When the user exits a TYPE_ENUM field, the validation procedure tries to complete the data in the buffer to a valid entry. If a complete choice string has been entered, it is of course valid. But it is also possible to enter a prefix of a valid string and have it completed for you.

By default, if you enter such a prefix and it matches more than one value in the string list, the prefix will be completed to the first matching value. But the checkunique argument, if true, requires prefix matches to be unique in order to be valid.

The REQ_NEXT_CHOICE and REQ_PREV_CHOICE input requests can be particularly useful with these fields.

TYPE_INTEGER

This field type accepts an integer. It is set up as follows:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_INTEGER,          /* type to associate */
                   int padding,           /* # places to zero-pad to */
                   int vmin, int vmax);   /* valid range */

Valid characters consist of an optional leading minus and digits. The range check is performed on exit. If the range maximum is less than or equal to the minimum, the range is ignored.

If the value passes its range check, it is padded with as many leading zero digits as necessary to meet the padding argument.

A TYPE_INTEGER value buffer can conveniently be interpreted with the C library function atoi(3).

TYPE_NUMERIC

This field type accepts a decimal number. It is set up as follows:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_NUMERIC,          /* type to associate */
                   int padding,           /* # places of precision */
                   int vmin, int vmax);   /* valid range */

Valid characters consist of an optional leading minus and digits. possibly including a decimal point. The range check is performed on exit. If the range maximum is less than or equal to the minimum, the range is ignored.

If the value passes its range check, it is padded with as many trailing zero digits as necessary to meet the padding argument.

A TYPE_NUMERIC value buffer can conveniently be interpreted with the C library function atof(3).

TYPE_REGEXP

This field type accepts data matching a regular expression. It is set up as follows:

int set_field_type(FIELD *field,          /* field to alter */
                   TYPE_REGEXP,           /* type to associate */
                   char *regexp);         /* expression to match */

The syntax for regular expressions is that of regcomp(3). The check for regular-expression match is performed on exit.

18.6. Form Driver: The work horse of the forms system

As in the menu system, form_driver() plays a very important role in forms system. All types of requests to forms system should be funneled through form_driver().

int form_driver(FORM *form,     /* form on which to operate     */
                int request)    /* form request code         */

As you have seen some of the examples above, you have to be in a loop looking for user input and then decide whether it's a field data or a form request. The form requests are then passed to form_driver() to do the work.

The requests roughly can be divided into following categories. Different requests and their usage is explained below:

18.6.1. Page Navigation Requests

These requests cause page-level moves through the form, triggering display of a new form screen. A form can be made of multiple pages. If you have a big form with lot of fields and logical sections, then you can divide the form into pages. The function set_new_page() to set a new page at the field specified.

int set_new_page(FIELD *field,/* Field at which page break to be set or unset */
         bool new_page_flag); /* should be TRUE to put a break */

The following requests allow you to move to different pages

  • REQ_NEXT_PAGE Move to the next form page.

  • REQ_PREV_PAGE Move to the previous form page.

  • REQ_FIRST_PAGE Move to the first form page.

  • REQ_LAST_PAGE Move to the last form page.

These requests treat the list as cyclic; that is, REQ_NEXT_PAGE from the last page goes to the first, and REQ_PREV_PAGE from the first page goes to the last.

18.6.2. Inter-Field Navigation Requests

These requests handle navigation between fields on the same page.

  • REQ_NEXT_FIELD Move to next field.

  • REQ_PREV_FIELD Move to previous field.

  • REQ_FIRST_FIELD Move to the first field.

  • REQ_LAST_FIELD Move to the last field.

  • REQ_SNEXT_FIELD Move to sorted next field.

  • REQ_SPREV_FIELD Move to sorted previous field.

  • REQ_SFIRST_FIELD Move to the sorted first field.

  • REQ_SLAST_FIELD Move to the 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.

These requests treat the list of fields on a page as cyclic; that is, REQ_NEXT_FIELD from the last field goes to the first, and REQ_PREV_FIELD from the first field goes to the last. The order of the fields for these (and the REQ_FIRST_FIELD and REQ_LAST_FIELD requests) is simply the order of the field pointers in the form array (as set up by new_form() or set_form_fields()

It is also possible to traverse the fields as if they had been sorted in screen-position order, so the sequence goes left-to-right and top-to-bottom. To do this, use the second group of four sorted-movement requests.

Finally, it is possible to move between fields using visual directions up, down, right, and left. To accomplish this, use the third group of four requests. Note, however, that the position of a form for purposes of these requests is its upper-left corner.

For example, suppose you have a multi-line field B, and two single-line fields A and C on the same line with B, with A to the left of B and C to the right of B. A REQ_MOVE_RIGHT from A will go to B only if A, B, and C all share the same first line; otherwise it will skip over B to C.

18.6.3. Intra-Field Navigation Requests

These requests drive movement of the edit cursor within the currently selected field.

  • REQ_NEXT_CHAR Move to next character.

  • REQ_PREV_CHAR Move to previous character.

  • REQ_NEXT_LINE Move to next line.

  • REQ_PREV_LINE Move to previous line.

  • REQ_NEXT_WORD Move to next word.

  • REQ_PREV_WORD Move to previous word.

  • REQ_BEG_FIELD Move to beginning of field.

  • REQ_END_FIELD Move to end of field.

  • REQ_BEG_LINE Move to beginning of line.

  • REQ_END_LINE Move to end of 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.

Each word is separated from the previous and next characters by whitespace. The commands to move to beginning and end of line or field look for the first or last non-pad character in their ranges.

18.6.4. Scrolling Requests

Fields that are dynamic and have grown and fields explicitly created with offscreen rows are scrollable. One-line fields scroll horizontally; multi-line fields scroll vertically. Most scrolling is triggered by editing and intra-field movement (the library scrolls the field to keep the cursor visible). It is possible to explicitly request scrolling with the following requests:

  • REQ_SCR_FLINE Scroll vertically forward a line.

  • REQ_SCR_BLINE Scroll vertically backward a line.

  • REQ_SCR_FPAGE Scroll vertically forward a page.

  • REQ_SCR_BPAGE Scroll vertically backward a page.

  • REQ_SCR_FHPAGE Scroll vertically forward half a page.

  • REQ_SCR_BHPAGE Scroll vertically backward half a page.

  • REQ_SCR_FCHAR Scroll horizontally forward a character.

  • REQ_SCR_BCHAR Scroll horizontally backward a character.

  • REQ_SCR_HFLINE Scroll horizontally one field width forward.

  • REQ_SCR_HBLINE Scroll horizontally one field width backward.

  • REQ_SCR_HFHALF Scroll horizontally one half field width forward.

  • REQ_SCR_HBHALF Scroll horizontally one half field width backward.

For scrolling purposes, a page of a field is the height of its visible part.

18.6.5. Editing Requests

When you pass the forms driver an ASCII character, it is treated as a request to add the character to the field's data buffer. Whether this is an insertion or a replacement depends on the field's edit mode (insertion is the default.

The following requests support editing the field and changing the edit mode:

  • REQ_INS_MODE Set insertion mode.

  • REQ_OVL_MODE Set overlay mode.

  • REQ_NEW_LINE New line request (see below for explanation).

  • REQ_INS_CHAR Insert space at character location.

  • REQ_INS_LINE Insert blank line at character location.

  • REQ_DEL_CHAR Delete character at cursor.

  • REQ_DEL_PREV Delete previous word at 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 behavior of the REQ_NEW_LINE and REQ_DEL_PREV requests is complicated and partly controlled by a pair of forms options. The special cases are triggered when the cursor is at the beginning of a field, or on the last line of the field.

First, we consider REQ_NEW_LINE:

The normal behavior of REQ_NEW_LINE in insert mode is to break the current line at the position of the edit cursor, inserting the portion of the current line after the cursor as a new line following the current and moving the cursor to the beginning of that new line (you may think of this as inserting a newline in the field buffer).

The normal behavior of REQ_NEW_LINE in overlay mode is to clear the current line from the position of the edit cursor to end of line. The cursor is then moved to the beginning of the next line.

However, REQ_NEW_LINE at the beginning of a field, or on the last line of a field, instead does a REQ_NEXT_FIELD. O_NL_OVERLOAD option is off, this special action is disabled.

Now, let us consider REQ_DEL_PREV:

The normal behavior of REQ_DEL_PREV is to delete the previous character. If insert mode is on, and the cursor is at the start of a line, and the text on that line will fit on the previous one, it instead appends the contents of the current line to the previous one and deletes the current line (you may think of this as deleting a newline from the field buffer).

However, REQ_DEL_PREV at the beginning of a field is instead treated as a REQ_PREV_FIELD.

If the O_BS_OVERLOAD option is off, this special action is disabled and the forms driver just returns E_REQUEST_DENIED.

18.6.6. Order Requests

If the type of your field is ordered, and has associated functions for getting the next and previous values of the type from a given value, there are requests that can fetch that value into the field buffer:

  • REQ_NEXT_CHOICE Place the successor value of the current value in the buffer.

  • REQ_PREV_CHOICE Place the predecessor value of the current value in the buffer.

Of the built-in field types, only TYPE_ENUM has built-in successor and predecessor functions. When you define a field type of your own (see Custom Validation Types), you can associate our own ordering functions.

18.6.7. Application Commands

Form requests are represented as integers above the curses value greater than KEY_MAX and less than or equal to the constant MAX_COMMAND. A value within this range gets ignored by form_driver(). So this can be used for any purpose by the application. It can be treated as an application specific action and take corresponding action.