Home | History | Annotate | Line # | Download | only in libform
internals.c revision 1.19
      1 /*	$NetBSD: internals.c,v 1.19 2001/06/04 11:44:30 blymn Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1998-1999 Brett Lymn
      5  *                         (blymn (at) baea.com.au, brett_lymn (at) yahoo.com.au)
      6  * All rights reserved.
      7  *
      8  * This code has been donated to The NetBSD Foundation by the Author.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. The name of the author may not be used to endorse or promote products
     16  *    derived from this software without specific prior written permission
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  *
     29  *
     30  */
     31 
     32 #include <limits.h>
     33 #include <ctype.h>
     34 #include <stdio.h>
     35 #include <stdlib.h>
     36 #include <strings.h>
     37 #include <assert.h>
     38 #include "internals.h"
     39 #include "form.h"
     40 
     41 #ifdef DEBUG
     42 /*
     43  *  file handle to write debug info to, this will be initialised when
     44  *  the form is first posted.
     45  */
     46 FILE *dbg = NULL;
     47 #endif
     48 
     49 /* define our own min function - this is not generic but will do here
     50  * (don't believe me?  think about what value you would get
     51  * from min(x++, y++)
     52  */
     53 #define min(a,b) (((a) > (b))? (b) : (a))
     54 
     55 /* for the line joining function... */
     56 #define JOIN_NEXT    1
     57 #define JOIN_NEXT_NW 2 /* next join, don't wrap the joined line */
     58 #define JOIN_PREV    3
     59 #define JOIN_PREV_NW 4 /* previous join, don't wrap the joined line */
     60 
     61 /* for the bump_lines function... */
     62 #define _FORMI_USE_CURRENT -1 /* indicates current cursor pos to be used */
     63 
     64 static void
     65 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val);
     66 static void
     67 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val);
     68 static int
     69 _formi_join_line(FIELD *field, unsigned int pos, int direction);
     70 void
     71 _formi_hscroll_back(FIELD *field, unsigned int amt);
     72 void
     73 _formi_hscroll_fwd(FIELD *field, unsigned int amt);
     74 static void
     75 _formi_scroll_back(FIELD *field, unsigned int amt);
     76 static void
     77 _formi_scroll_fwd(FIELD *field, unsigned int amt);
     78 static int
     79 find_sow(char *str, unsigned int offset);
     80 static int
     81 find_cur_line(FIELD *cur, unsigned pos);
     82 static int
     83 split_line(FIELD *field, unsigned pos);
     84 static void
     85 bump_lines(FIELD *field, int pos, int amt);
     86 
     87 
     88 /*
     89  * Open the debug file if it is not already open....
     90  */
     91 #ifdef DEBUG
     92 int
     93 _formi_create_dbg_file(void)
     94 {
     95 	if (dbg == NULL) {
     96 		dbg = fopen("___form_dbg.out", "w");
     97 		if (dbg == NULL) {
     98 			fprintf(stderr, "Cannot open debug file!\n");
     99 			return E_SYSTEM_ERROR;
    100 		}
    101 	}
    102 
    103 	return E_OK;
    104 }
    105 #endif
    106 
    107 /*
    108  * Bump the lines array elements in the given field by the given amount.
    109  * The row to start acting on can either be inferred from the given position
    110  * or if the special value _FORMI_USE_CURRENT is set then the row will be
    111  * the row the cursor is currently on.
    112  */
    113 static void
    114 bump_lines(FIELD *field, int pos, int amt)
    115 {
    116 	int i, row;
    117 #ifdef DEBUG
    118 	int dbg_ok = FALSE;
    119 #endif
    120 
    121 	if (pos == _FORMI_USE_CURRENT)
    122 		row = field->start_line + field->cursor_ypos;
    123 	else
    124 		row = find_cur_line(field, (unsigned) pos);
    125 
    126 #ifdef DEBUG
    127 	if (_formi_create_dbg_file() == E_OK) {
    128 		dbg_ok = TRUE;
    129 		fprintf(dbg, "bump_lines: bump starting at row %d\n", row);
    130 		fprintf(dbg,
    131 			"bump_lines: len from %d to %d, end from %d to %d\n",
    132 			field->lines[row].length,
    133 			field->lines[row].length + amt,
    134 			field->lines[row].end, field->lines[row].end + amt);
    135 	}
    136 #endif
    137 
    138 	if (((int)field->lines[row].length + amt) < 0) {
    139 		field->lines[row].length = 0;
    140 		field->lines[row].end = 0;
    141 	} else
    142 		field->lines[row].length += amt;
    143 
    144 	if (field->lines[row].length > 1)
    145 		field->lines[row].end += amt;
    146 	else
    147 		field->lines[row].end = field->lines[row].start;
    148 
    149 	for (i = row + 1; i < field->row_count; i++) {
    150 #ifdef DEBUG
    151 		if (dbg_ok) {
    152 			fprintf(dbg,
    153 		"bump_lines: row %d: len from %d to %d, end from %d to %d\n",
    154 				i, field->lines[i].start,
    155 				field->lines[i].start + amt,
    156 				field->lines[i].end,
    157 				field->lines[i].end + amt);
    158 		}
    159 		fflush(dbg);
    160 #endif
    161 		field->lines[i].start += amt;
    162 		field->lines[i].end += amt;
    163 	}
    164 }
    165 
    166 /*
    167  * Set the form's current field to the first valid field on the page.
    168  * Assume the fields have been sorted and stitched.
    169  */
    170 int
    171 _formi_pos_first_field(FORM *form)
    172 {
    173 	FIELD *cur;
    174 	int old_page;
    175 
    176 	old_page = form->page;
    177 
    178 	  /* scan forward for an active page....*/
    179 	while (form->page_starts[form->page].in_use == 0) {
    180 		form->page++;
    181 		if (form->page > form->max_page) {
    182 			form->page = old_page;
    183 			return E_REQUEST_DENIED;
    184 		}
    185 	}
    186 
    187 	  /* then scan for a field we can use */
    188 	cur = form->fields[form->page_starts[form->page].first];
    189 	while ((cur->opts & (O_VISIBLE | O_ACTIVE))
    190 	       != (O_VISIBLE | O_ACTIVE)) {
    191 		cur = CIRCLEQ_NEXT(cur, glue);
    192 		if (cur == (void *) &form->sorted_fields) {
    193 			form->page = old_page;
    194 			return E_REQUEST_DENIED;
    195 		}
    196 	}
    197 
    198 	form->cur_field = cur->index;
    199 	return E_OK;
    200 }
    201 
    202 /*
    203  * Set the field to the next active and visible field, the fields are
    204  * traversed in index order in the direction given.  If the parameter
    205  * use_sorted is TRUE then the sorted field list will be traversed instead
    206  * of using the field index.
    207  */
    208 int
    209 _formi_pos_new_field(FORM *form, unsigned direction, unsigned use_sorted)
    210 {
    211 	FIELD *cur;
    212 	int i;
    213 
    214 	i = form->cur_field;
    215 	cur = form->fields[i];
    216 
    217 	do {
    218 		if (direction == _FORMI_FORWARD) {
    219 			if (use_sorted == TRUE) {
    220 				if ((form->wrap == FALSE) &&
    221 				    (cur == CIRCLEQ_LAST(&form->sorted_fields)))
    222 					return E_REQUEST_DENIED;
    223 				cur = CIRCLEQ_NEXT(cur, glue);
    224 				i = cur->index;
    225 			} else {
    226 				if ((form->wrap == FALSE) &&
    227 				    ((i + 1) >= form->field_count))
    228 					return E_REQUEST_DENIED;
    229 				i++;
    230 				if (i >= form->field_count)
    231 					i = 0;
    232 			}
    233 		} else {
    234 			if (use_sorted == TRUE) {
    235 				if ((form->wrap == FALSE) &&
    236 				    (cur == CIRCLEQ_FIRST(&form->sorted_fields)))
    237 					return E_REQUEST_DENIED;
    238 				cur = CIRCLEQ_PREV(cur, glue);
    239 				i = cur->index;
    240 			} else {
    241 				if ((form->wrap == FALSE) && (i <= 0))
    242 					return E_REQUEST_DENIED;
    243 				i--;
    244 				if (i < 0)
    245 					i = form->field_count - 1;
    246 			}
    247 		}
    248 
    249 		if ((form->fields[i]->opts & (O_VISIBLE | O_ACTIVE))
    250 			== (O_VISIBLE | O_ACTIVE)) {
    251 			form->cur_field = i;
    252 			return E_OK;
    253 		}
    254 	}
    255 	while (i != form->cur_field);
    256 
    257 	return E_REQUEST_DENIED;
    258 }
    259 
    260 /*
    261  * Find the line in a field that the cursor is currently on.
    262  */
    263 static int
    264 find_cur_line(FIELD *cur, unsigned pos)
    265 {
    266 	unsigned row;
    267 
    268 	  /* first check if pos is at the end of the string, if this
    269 	   * is true then just return the last row since the pos may
    270 	   * not have been added to the lines array yet.
    271 	   */
    272 	if (pos == (cur->buffers[0].length - 1))
    273 		return (cur->row_count - 1);
    274 
    275 	for (row = 0; row < cur->row_count; row++) {
    276 		if ((pos >= cur->lines[row].start)
    277 		    && (pos <= cur->lines[row].end))
    278 			return row;
    279 	}
    280 
    281 #ifdef DEBUG
    282 	  /* barf if we get here, this should not be possible */
    283 	assert((row != row));
    284 #endif
    285 	return 0;
    286 }
    287 
    288 
    289 /*
    290  * Word wrap the contents of the field's buffer 0 if this is allowed.
    291  * If the wrap is successful, that is, the row count nor the buffer
    292  * size is exceeded then the function will return E_OK, otherwise it
    293  * will return E_REQUEST_DENIED.
    294  */
    295 int
    296 _formi_wrap_field(FIELD *field, unsigned int loc)
    297 {
    298 	char *str;
    299 	int width, row, start_row;
    300 	unsigned int pos;
    301 
    302 	str = field->buffers[0].string;
    303 
    304 	  /* Don't bother if the field string is too short. */
    305 	if (field->buffers[0].length < field->cols)
    306 		return E_OK;
    307 
    308 	if ((field->opts & O_STATIC) == O_STATIC) {
    309 		if ((field->rows + field->nrows) == 1) {
    310 			return E_OK; /* cannot wrap a single line */
    311 		}
    312 		width = field->cols;
    313 	} else {
    314 		  /* if we are limited to one line then don't try to wrap */
    315 		if ((field->drows + field->nrows) == 1) {
    316 			return E_OK;
    317 		}
    318 
    319 		  /*
    320 		   * hueristic - if a dynamic field has more than one line
    321 		   * on the screen then the field grows rows, otherwise
    322 		   * it grows columns, effectively a single line field.
    323 		   * This is documented AT&T behaviour.
    324 		   */
    325 		if (field->rows > 1) {
    326 			width = field->cols;
    327 		} else {
    328 			return E_OK;
    329 		}
    330 	}
    331 
    332 	start_row = find_cur_line(field, loc);
    333 
    334 	  /* if we are not at the top of the field then back up one
    335 	   * row because we may be able to merge the current row into
    336 	   * the one above.
    337 	   */
    338 	if (start_row > 0)
    339 		start_row--;
    340 
    341 	for (row = start_row; row < field->row_count; row++) {
    342 	  AGAIN:
    343 		pos = field->lines[row].end;
    344 		if (field->lines[row].length < width) {
    345 			  /* line may be too short, try joining some lines */
    346 
    347 			if ((((int) field->row_count) - 1) == row) {
    348 				/* if this is the last row then don't
    349 				 * wrap
    350 				 */
    351 				continue;
    352 			}
    353 
    354 			if (_formi_join_line(field, (unsigned int) pos,
    355 					     JOIN_NEXT_NW) == E_OK) {
    356 				goto AGAIN;
    357 			} else
    358 				break;
    359 		} else {
    360 			  /* line is too long, split it - maybe */
    361 
    362 			  /* first check if we have not run out of room */
    363 			if ((field->opts & O_STATIC) == O_STATIC) {
    364 				/* check static field */
    365 				if ((field->rows + field->nrows - 1) == row)
    366 					return E_REQUEST_DENIED;
    367 			} else {
    368 				/* check dynamic field */
    369 				if ((field->max != 0)
    370 				    && ((field->max - 1) == row))
    371 					return E_REQUEST_DENIED;
    372 			}
    373 
    374 			  /* split on first whitespace before current word */
    375 			pos = width + field->lines[row].start - 1;
    376 			if (pos >= field->buffers[0].length)
    377 				pos = field->buffers[0].length - 1;
    378 
    379 			if ((!isblank(str[pos])) &&
    380 			    ((field->opts & O_WRAP) == O_WRAP)) {
    381 				if (!isblank(str[pos - 1]))
    382 					pos = find_sow(str,
    383 						       (unsigned int) pos);
    384 				/*
    385 				 * If we cannot split the line then return
    386 				 * NO_ROOM so the driver can tell that it
    387 				 * should not autoskip (if that is enabled)
    388 				 */
    389 				if ((pos == 0) || (!isblank(str[pos - 1]))
    390 				    || ((pos <= field->lines[row].start)
    391 					&& (field->buffers[0].length
    392 					    >= (width - 1
    393 						+ field->lines[row].start)))) {
    394 					return E_NO_ROOM;
    395 				}
    396 			}
    397 
    398 			  /* if we are at the end of the string and it has
    399 			   * a trailing blank, don't wrap the blank.
    400 			   */
    401 			if ((pos == field->buffers[0].length - 1) &&
    402 			    (isblank(str[pos])))
    403 				continue;
    404 
    405 			  /*
    406 			   * otherwise, if we are still sitting on a
    407 			   * blank but not at the end of the line
    408 			   * move forward one char so the blank
    409 			   * is on the line boundary.
    410 			   */
    411 			if (isblank(str[pos]))
    412 				pos++;
    413 
    414 			if (split_line(field, pos) != E_OK) {
    415 				return E_REQUEST_DENIED;
    416 			}
    417 		}
    418 	}
    419 
    420 	return E_OK;
    421 }
    422 
    423 /*
    424  * Join the two lines that surround the location pos, the type
    425  * variable indicates the direction of the join, JOIN_NEXT will join
    426  * the next line to the current line, JOIN_PREV will join the current
    427  * line to the previous line, the new lines will be wrapped unless the
    428  * _NW versions of the directions are used.  Returns E_OK if the join
    429  * was successful or E_REQUEST_DENIED if the join cannot happen.
    430  */
    431 static int
    432 _formi_join_line(FIELD *field, unsigned int pos, int direction)
    433 {
    434 	unsigned int row, i;
    435 	int old_alloced, old_row_count;
    436 	struct _formi_field_lines *saved;
    437 #ifdef DEBUG
    438 	int dbg_ok = FALSE;
    439 
    440 	if (_formi_create_dbg_file() == E_OK) {
    441 		dbg_ok = TRUE;
    442 	}
    443 #endif
    444 
    445 	if ((saved = (struct _formi_field_lines *)
    446 	     malloc(field->lines_alloced * sizeof(struct _formi_field_lines)))
    447 	    == NULL)
    448 		return E_REQUEST_DENIED;
    449 
    450 	bcopy(field->lines, saved,
    451 	      field->row_count * sizeof(struct _formi_field_lines));
    452 	old_alloced = field->lines_alloced;
    453 	old_row_count = field->row_count;
    454 
    455 	row = find_cur_line(field, pos);
    456 
    457 #ifdef DEBUG
    458 	if (dbg_ok == TRUE) {
    459 		fprintf(dbg, "join_line: working on row %d, row_count = %d\n",
    460 			row, field->row_count);
    461 	}
    462 #endif
    463 
    464 	if ((direction == JOIN_NEXT) || (direction == JOIN_NEXT_NW)) {
    465 		  /* see if there is another line following... */
    466 		if (row == (field->row_count - 1)) {
    467 			free(saved);
    468 			return E_REQUEST_DENIED;
    469 		}
    470 
    471 #ifdef DEBUG
    472 		if (dbg_ok == TRUE) {
    473 			fprintf(dbg,
    474 			"join_line: join_next before end = %d, length = %d",
    475 				field->lines[row].end,
    476 				field->lines[row].length);
    477 			fprintf(dbg,
    478 				" :: next row end = %d, length = %d\n",
    479 				field->lines[row + 1].end,
    480 				field->lines[row + 1].length);
    481 		}
    482 #endif
    483 
    484 		field->lines[row].end = field->lines[row + 1].end;
    485 		field->lines[row].length += field->lines[row + 1].length;
    486 		  /* shift all the remaining lines up.... */
    487 		for (i = row + 2; i < field->row_count; i++)
    488 			field->lines[i - 1] = field->lines[i];
    489 	} else {
    490 		if ((pos == 0) || (row == 0)) {
    491 			free(saved);
    492 			return E_REQUEST_DENIED;
    493 		}
    494 
    495 #ifdef DEBUG
    496 		if (dbg_ok == TRUE) {
    497 			fprintf(dbg,
    498 			"join_line: join_prev before end = %d, length = %d",
    499 				field->lines[row].end,
    500 				field->lines[row].length);
    501 			fprintf(dbg,
    502 				" :: prev row end = %d, length = %d\n",
    503 				field->lines[row - 1].end,
    504 				field->lines[row - 1].length);
    505 		}
    506 #endif
    507 
    508 		field->lines[row - 1].end = field->lines[row].end;
    509 		field->lines[row - 1].length += field->lines[row].length;
    510 		  /* shift all the remaining lines up */
    511 		for (i = row + 1; i < field->row_count; i++)
    512 			field->lines[i - 1] = field->lines[i];
    513 	}
    514 
    515 #ifdef DEBUG
    516 	if (dbg_ok == TRUE) {
    517 		fprintf(dbg,
    518 			"join_line: exit end = %d, length = %d\n",
    519 			field->lines[row].end, field->lines[row].length);
    520 	}
    521 #endif
    522 
    523 	field->row_count--;
    524 
    525 	  /* wrap the field if required, if this fails undo the change */
    526 	if ((direction == JOIN_NEXT) || (direction == JOIN_PREV)) {
    527 		if (_formi_wrap_field(field, (unsigned int) pos) != E_OK) {
    528 			free(field->lines);
    529 			field->lines = saved;
    530 			field->lines_alloced = old_alloced;
    531 			field->row_count = old_row_count;
    532 			return E_REQUEST_DENIED;
    533 		}
    534 	}
    535 
    536 	free(saved);
    537 	return E_OK;
    538 }
    539 
    540 /*
    541  * Split the line at the given position, if possible
    542  */
    543 static int
    544 split_line(FIELD *field, unsigned pos)
    545 {
    546 	struct _formi_field_lines *new_lines;
    547 	unsigned int row, i;
    548 #ifdef DEBUG
    549 	short dbg_ok = FALSE;
    550 #endif
    551 
    552 	if (pos == 0)
    553 		return E_REQUEST_DENIED;
    554 
    555 #ifdef DEBUG
    556 	if (_formi_create_dbg_file() == E_OK) {
    557 		fprintf(dbg, "split_line: splitting line at %d\n", pos);
    558 		dbg_ok = TRUE;
    559 	}
    560 #endif
    561 
    562 	if ((field->row_count + 1) > field->lines_alloced) {
    563 		if ((new_lines = (struct _formi_field_lines *)
    564 		     realloc(field->lines, (field->row_count + 1)
    565 			     * sizeof(struct _formi_field_lines))) == NULL)
    566 			return E_SYSTEM_ERROR;
    567 		field->lines = new_lines;
    568 		field->lines_alloced++;
    569 	}
    570 
    571 	row = find_cur_line(field, pos);
    572 #ifdef DEBUG
    573 	if (dbg_ok == TRUE) {
    574 		fprintf(dbg,
    575 	"split_line: enter: lines[%d].end = %d, lines[%d].length = %d\n",
    576 			row, field->lines[row].end, row,
    577 			field->lines[row].length);
    578 	}
    579 
    580 	assert(((field->lines[row].end < INT_MAX) &&
    581 		(field->lines[row].length < INT_MAX) &&
    582 		(field->lines[row].length > 0)));
    583 
    584 #endif
    585 
    586 	  /* if asked to split right where the line already starts then
    587 	   * just return - nothing to do.
    588 	   */
    589 	if (field->lines[row].start == pos)
    590 		return E_OK;
    591 
    592 	for (i = field->row_count - 1; i > row; i--) {
    593 		field->lines[i + 1] = field->lines[i];
    594 	}
    595 
    596 	field->lines[row + 1].end = field->lines[row].end;
    597 	field->lines[row].end = pos - 1;
    598 	field->lines[row].length = pos - field->lines[row].start;
    599 	field->lines[row + 1].start = pos;
    600 	field->lines[row + 1].length = field->lines[row + 1].end
    601 		- field->lines[row + 1].start + 1;
    602 
    603 #ifdef DEBUG
    604 	assert(((field->lines[row + 1].end < INT_MAX) &&
    605 		(field->lines[row].end < INT_MAX) &&
    606 		(field->lines[row].length < INT_MAX) &&
    607 		(field->lines[row + 1].start < INT_MAX) &&
    608 		(field->lines[row + 1].length < INT_MAX) &&
    609 		(field->lines[row].length > 0) &&
    610 		(field->lines[row + 1].length > 0)));
    611 
    612 	if (dbg_ok == TRUE) {
    613 		fprintf(dbg,
    614 	"split_line: exit: lines[%d].end = %d, lines[%d].length = %d, ",
    615 			row, field->lines[row].end, row,
    616 			field->lines[row].length);
    617 		fprintf(dbg, "lines[%d].start = %d, lines[%d].end = %d, ",
    618 			row + 1, field->lines[row + 1].start, row + 1,
    619 			field->lines[row + 1].end);
    620 		fprintf(dbg, "lines[%d].length = %d, row_count = %d\n",
    621 			row + 1, field->lines[row + 1].length,
    622 			field->row_count + 1);
    623 	}
    624 #endif
    625 
    626 	field->row_count++;
    627 
    628 #ifdef DEBUG
    629 	if (dbg_ok == TRUE) {
    630 		bump_lines(field, 0, 0); /* will report line data for us */
    631 	}
    632 #endif
    633 
    634 	return E_OK;
    635 }
    636 
    637 /*
    638  * skip the blanks in the given string, start at the index start and
    639  * continue forward until either the end of the string or a non-blank
    640  * character is found.  Return the index of either the end of the string or
    641  * the first non-blank character.
    642  */
    643 unsigned
    644 _formi_skip_blanks(char *string, unsigned int start)
    645 {
    646 	unsigned int i;
    647 
    648 	i = start;
    649 
    650 	while ((string[i] != '\0') && isblank(string[i]))
    651 		i++;
    652 
    653 	return i;
    654 }
    655 
    656 /*
    657  * Return the index of the top left most field of the two given fields.
    658  */
    659 static int
    660 _formi_top_left(FORM *form, int a, int b)
    661 {
    662 	  /* lower row numbers always win here.... */
    663 	if (form->fields[a]->form_row < form->fields[b]->form_row)
    664 		return a;
    665 
    666 	if (form->fields[a]->form_row > form->fields[b]->form_row)
    667 		return b;
    668 
    669 	  /* rows must be equal, check columns */
    670 	if (form->fields[a]->form_col < form->fields[b]->form_col)
    671 		return a;
    672 
    673 	if (form->fields[a]->form_col > form->fields[b]->form_col)
    674 		return b;
    675 
    676 	  /* if we get here fields must be in exactly the same place, punt */
    677 	return a;
    678 }
    679 
    680 /*
    681  * Return the index to the field that is the bottom-right-most of the
    682  * two given fields.
    683  */
    684 static int
    685 _formi_bottom_right(FORM *form, int a, int b)
    686 {
    687 	  /* check the rows first, biggest row wins */
    688 	if (form->fields[a]->form_row > form->fields[b]->form_row)
    689 		return a;
    690 	if (form->fields[a]->form_row < form->fields[b]->form_row)
    691 		return b;
    692 
    693 	  /* rows must be equal, check cols, biggest wins */
    694 	if (form->fields[a]->form_col > form->fields[b]->form_col)
    695 		return a;
    696 	if (form->fields[a]->form_col < form->fields[b]->form_col)
    697 		return b;
    698 
    699 	  /* fields in the same place, punt */
    700 	return a;
    701 }
    702 
    703 /*
    704  * Find the end of the current word in the string str, starting at
    705  * offset - the end includes any trailing whitespace.  If the end of
    706  * the string is found before a new word then just return the offset
    707  * to the end of the string.
    708  */
    709 static int
    710 find_eow(char *str, unsigned int offset)
    711 {
    712 	int start;
    713 
    714 	start = offset;
    715 	  /* first skip any non-whitespace */
    716 	while ((str[start] != '\0') && !isblank(str[start]))
    717 		start++;
    718 
    719 	  /* see if we hit the end of the string */
    720 	if (str[start] == '\0')
    721 		return start;
    722 
    723 	  /* otherwise skip the whitespace.... */
    724 	while ((str[start] != '\0') && isblank(str[start]))
    725 		start++;
    726 
    727 	return start;
    728 }
    729 
    730 /*
    731  * Find the beginning of the current word in the string str, starting
    732  * at offset.
    733  */
    734 static int
    735 find_sow(char *str, unsigned int offset)
    736 {
    737 	int start;
    738 
    739 	start = offset;
    740 
    741 	if (start > 0) {
    742 		if (isblank(str[start]) || isblank(str[start - 1])) {
    743 			if (isblank(str[start - 1]))
    744 				start--;
    745 			  /* skip the whitespace.... */
    746 			while ((start >= 0) && isblank(str[start]))
    747 				start--;
    748 		}
    749 	}
    750 
    751 	  /* see if we hit the start of the string */
    752 	if (start < 0)
    753 		return 0;
    754 
    755 	  /* now skip any non-whitespace */
    756 	while ((start >= 0) && !isblank(str[start]))
    757 		start--;
    758 
    759 	if (start > 0)
    760 		start++; /* last loop has us pointing at a space, adjust */
    761 
    762 	if (start < 0)
    763 		start = 0;
    764 
    765 	return start;
    766 }
    767 
    768 /*
    769  * Scroll the field forward the given number of lines.
    770  */
    771 static void
    772 _formi_scroll_fwd(FIELD *field, unsigned int amt)
    773 {
    774 	  /* check if we have lines to scroll */
    775 	if (field->row_count < (field->start_line + field->rows))
    776 		return;
    777 
    778 	field->start_line += min(amt,
    779 				 field->row_count - field->start_line
    780 				 - field->rows);
    781 }
    782 
    783 /*
    784  * Scroll the field backward the given number of lines.
    785  */
    786 static void
    787 _formi_scroll_back(FIELD *field, unsigned int amt)
    788 {
    789 	if (field->start_line == 0)
    790 		return;
    791 
    792 	field->start_line -= min(field->start_line, amt);
    793 }
    794 
    795 /*
    796  * Scroll the field forward the given number of characters.
    797  */
    798 void
    799 _formi_hscroll_fwd(FIELD *field, int unsigned amt)
    800 {
    801 	field->start_char += min(amt,
    802 		field->lines[field->start_line + field->cursor_ypos].end);
    803 }
    804 
    805 /*
    806  * Scroll the field backward the given number of characters.
    807  */
    808 void
    809 _formi_hscroll_back(FIELD *field, unsigned int amt)
    810 {
    811 	field->start_char -= min(field->start_char, amt);
    812 }
    813 
    814 /*
    815  * Find the different pages in the form fields and assign the form
    816  * page_starts array with the information to find them.
    817  */
    818 int
    819 _formi_find_pages(FORM *form)
    820 {
    821 	int i, cur_page = 0;
    822 
    823 	if ((form->page_starts = (_FORMI_PAGE_START *)
    824 	     malloc((form->max_page + 1) * sizeof(_FORMI_PAGE_START))) == NULL)
    825 		return E_SYSTEM_ERROR;
    826 
    827 	  /* initialise the page starts array */
    828 	memset(form->page_starts, 0,
    829 	       (form->max_page + 1) * sizeof(_FORMI_PAGE_START));
    830 
    831 	for (i =0; i < form->field_count; i++) {
    832 		if (form->fields[i]->page_break == 1)
    833 			cur_page++;
    834 		if (form->page_starts[cur_page].in_use == 0) {
    835 			form->page_starts[cur_page].in_use = 1;
    836 			form->page_starts[cur_page].first = i;
    837 			form->page_starts[cur_page].last = i;
    838 			form->page_starts[cur_page].top_left = i;
    839 			form->page_starts[cur_page].bottom_right = i;
    840 		} else {
    841 			form->page_starts[cur_page].last = i;
    842 			form->page_starts[cur_page].top_left =
    843 				_formi_top_left(form,
    844 						form->page_starts[cur_page].top_left,
    845 						i);
    846 			form->page_starts[cur_page].bottom_right =
    847 				_formi_bottom_right(form,
    848 						    form->page_starts[cur_page].bottom_right,
    849 						    i);
    850 		}
    851 	}
    852 
    853 	return E_OK;
    854 }
    855 
    856 /*
    857  * Completely redraw the field of the given form.
    858  */
    859 void
    860 _formi_redraw_field(FORM *form, int field)
    861 {
    862 	unsigned int pre, post, flen, slen, i, row, start, last_row;
    863 	char *str;
    864 	FIELD *cur;
    865 #ifdef DEBUG
    866 	char buffer[100];
    867 #endif
    868 
    869 	cur = form->fields[field];
    870 	str = cur->buffers[0].string;
    871 	flen = cur->cols;
    872 	slen = 0;
    873 	start = 0;
    874 
    875 	if ((cur->row_count - cur->start_line) < cur->rows)
    876 		last_row = cur->row_count;
    877 	else
    878 		last_row = cur->start_line + cur->rows;
    879 
    880 	for (row = cur->start_line; row < last_row; row++) {
    881 		wmove(form->scrwin,
    882 		      (int) (cur->form_row + row - cur->start_line),
    883 		      (int) cur->form_col);
    884 		start = cur->lines[row].start;
    885 		slen = cur->lines[row].length;
    886 
    887 		if ((cur->opts & O_STATIC) == O_STATIC) {
    888 			switch (cur->justification) {
    889 			case JUSTIFY_RIGHT:
    890 				post = 0;
    891 				if (flen < slen)
    892 					pre = 0;
    893 				else
    894 					pre = flen - slen;
    895 				break;
    896 
    897 			case JUSTIFY_CENTER:
    898 				if (flen < slen) {
    899 					pre = 0;
    900 					post = 0;
    901 				} else {
    902 					pre = flen - slen;
    903 					post = pre = pre / 2;
    904 					  /* get padding right if
    905                                              centring is not even */
    906 					if ((post + pre + slen) < flen)
    907 						post++;
    908 				}
    909 				break;
    910 
    911 			case NO_JUSTIFICATION:
    912 			case JUSTIFY_LEFT:
    913 			default:
    914 				pre = 0;
    915 				if (flen <= slen)
    916 					post = 0;
    917 				else {
    918 					post = flen - slen;
    919 					if (post > flen)
    920 						post = flen;
    921 				}
    922 				break;
    923 			}
    924 		} else {
    925 			  /* dynamic fields are not justified */
    926 			pre = 0;
    927 			if (flen <= slen)
    928 				post = 0;
    929 			else {
    930 				post = flen - slen;
    931 				if (post > flen)
    932 					post = flen;
    933 			}
    934 
    935 			  /* but they do scroll.... */
    936 
    937 			if (pre > cur->start_char - start)
    938 				pre = pre - cur->start_char + start;
    939 			else
    940 				pre = 0;
    941 
    942 			if (slen > cur->start_char) {
    943 				slen -= cur->start_char;
    944 				post += cur->start_char;
    945 				if (post > flen)
    946 					post = flen;
    947 			} else {
    948 				slen = 0;
    949 				post = flen - pre;
    950 			}
    951 		}
    952 
    953 		if (form->cur_field == field)
    954 			wattrset(form->scrwin, cur->fore);
    955 		else
    956 			wattrset(form->scrwin, cur->back);
    957 
    958 #ifdef DEBUG
    959 		if (_formi_create_dbg_file() == E_OK) {
    960 			fprintf(dbg,
    961   "redraw_field: start=%d, pre=%d, slen=%d, flen=%d, post=%d, start_char=%d\n",
    962 				start, pre, slen, flen, post, cur->start_char);
    963 			if (str != NULL) {
    964 				strncpy(buffer,
    965 					&str[cur->start_char
    966 					    + cur->lines[row].start], flen);
    967 			} else {
    968 				strcpy(buffer, "(null)");
    969 			}
    970 			buffer[flen] = '\0';
    971 			fprintf(dbg, "redraw_field: %s\n", buffer);
    972 		}
    973 #endif
    974 
    975 		for (i = start + cur->start_char; i < pre; i++)
    976 			waddch(form->scrwin, cur->pad);
    977 
    978 #ifdef DEBUG
    979 		fprintf(dbg, "redraw_field: will add %d chars\n",
    980 			min(slen, flen));
    981 #endif
    982 		for (i = 0; i < min(slen, flen); i++)
    983 		{
    984 #ifdef DEBUG
    985 			fprintf(dbg, "adding char str[%d]=%c\n",
    986 				i + cur->start_char + cur->lines[row].start,
    987 				str[i + cur->start_char
    988 				   + cur->lines[row].start]);
    989 #endif
    990 			if (((cur->opts & O_PUBLIC) != O_PUBLIC)) {
    991 				waddch(form->scrwin, cur->pad);
    992 			} else if ((cur->opts & O_VISIBLE) == O_VISIBLE) {
    993 				waddch(form->scrwin, str[i + cur->start_char
    994 				+ cur->lines[row].start]);
    995 			} else {
    996 				waddch(form->scrwin, ' ');
    997 			}
    998 		}
    999 
   1000 		for (i = 0; i < post; i++)
   1001 			waddch(form->scrwin, cur->pad);
   1002 	}
   1003 
   1004 	for (row = cur->row_count - cur->start_line; row < cur->rows; row++) {
   1005 		wmove(form->scrwin, (int) (cur->form_row + row),
   1006 		      (int) cur->form_col);
   1007 		for (i = 0; i < cur->cols; i++) {
   1008 			waddch(form->scrwin, cur->pad);
   1009 		}
   1010 	}
   1011 
   1012 	return;
   1013 }
   1014 
   1015 /*
   1016  * Display the fields attached to the form that are on the current page
   1017  * on the screen.
   1018  *
   1019  */
   1020 int
   1021 _formi_draw_page(FORM *form)
   1022 {
   1023 	int i;
   1024 
   1025 	if (form->page_starts[form->page].in_use == 0)
   1026 		return E_BAD_ARGUMENT;
   1027 
   1028 	wclear(form->scrwin);
   1029 
   1030 	for (i = form->page_starts[form->page].first;
   1031 	     i <= form->page_starts[form->page].last; i++)
   1032 		_formi_redraw_field(form, i);
   1033 
   1034 	return E_OK;
   1035 }
   1036 
   1037 /*
   1038  * Add the character c at the position pos in buffer 0 of the given field
   1039  */
   1040 int
   1041 _formi_add_char(FIELD *field, unsigned int pos, char c)
   1042 {
   1043 	char *new;
   1044 	unsigned int new_size;
   1045 	int status;
   1046 
   1047 	  /*
   1048 	   * If buffer has not had a string before, set it to a blank
   1049 	   * string.  Everything should flow from there....
   1050 	   */
   1051 	if (field->buffers[0].string == NULL) {
   1052 		set_field_buffer(field, 0, "");
   1053 	}
   1054 
   1055 	if (_formi_validate_char(field, c) != E_OK) {
   1056 #ifdef DEBUG
   1057 		fprintf(dbg, "add_char: char %c failed char validation\n", c);
   1058 #endif
   1059 		return E_INVALID_FIELD;
   1060 	}
   1061 
   1062 #ifdef DEBUG
   1063 	fprintf(dbg, "add_char: pos=%d, char=%c\n", pos, c);
   1064 	fprintf(dbg,
   1065 	   "add_char enter: xpos=%d, start=%d, length=%d(%d), allocated=%d\n",
   1066 		field->cursor_xpos, field->start_char,
   1067 		field->buffers[0].length, strlen(field->buffers[0].string),
   1068 		field->buffers[0].allocated);
   1069 	fprintf(dbg, "add_char enter: %s\n", field->buffers[0].string);
   1070 	fprintf(dbg, "add_char enter: buf0_status=%d\n", field->buf0_status);
   1071 #endif
   1072 	if (((field->opts & O_BLANK) == O_BLANK) &&
   1073 	    (field->buf0_status == FALSE) &&
   1074 	    ((field->cursor_xpos + field->start_char) == 0)) {
   1075 		field->buffers[0].length = 0;
   1076 		field->buffers[0].string[0] = '\0';
   1077 		pos = 0;
   1078 		field->start_char = 0;
   1079 		field->start_line = 0;
   1080 		field->row_count = 1;
   1081 		field->cursor_xpos = 0;
   1082 		field->cursor_ypos = 0;
   1083 		field->lines[0].start = 0;
   1084 		field->lines[0].end = 0;
   1085 		field->lines[0].length = 0;
   1086 	}
   1087 
   1088 
   1089 	if ((field->overlay == 0)
   1090 	    || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
   1091 		  /* first check if the field can have more chars...*/
   1092 		if ((((field->opts & O_STATIC) == O_STATIC) &&
   1093 		     (field->buffers[0].length >= (field->cols * field->rows))) ||
   1094 		    (((field->opts & O_STATIC) != O_STATIC) &&
   1095 /*XXXXX this is wrong - should check max row or col */
   1096 		     ((field->max > 0) &&
   1097 		      (field->buffers[0].length >= field->max))))
   1098 			return E_REQUEST_DENIED;
   1099 
   1100 		if (field->buffers[0].length + 1
   1101 		    >= field->buffers[0].allocated) {
   1102 			new_size = field->buffers[0].allocated + 64
   1103 				- (field->buffers[0].allocated % 64);
   1104 			if ((new = (char *) realloc(field->buffers[0].string,
   1105 						    new_size )) == NULL)
   1106 				return E_SYSTEM_ERROR;
   1107 			field->buffers[0].allocated = new_size;
   1108 			field->buffers[0].string = new;
   1109 		}
   1110 	}
   1111 
   1112 	if ((field->overlay == 0) && (field->buffers[0].length > pos)) {
   1113 		bcopy(&field->buffers[0].string[pos],
   1114 		      &field->buffers[0].string[pos + 1],
   1115 		      field->buffers[0].length - pos + 1);
   1116 	}
   1117 
   1118 	field->buffers[0].string[pos] = c;
   1119 	if (pos >= field->buffers[0].length) {
   1120 		  /* make sure the string is terminated if we are at the
   1121 		   * end of the string, the terminator would be missing
   1122 		   * if we are are at the end of the field.
   1123 		   */
   1124 		field->buffers[0].string[pos + 1] = '\0';
   1125 	}
   1126 
   1127 	  /* only increment the length if we are inserting characters
   1128 	   * OR if we are at the end of the field in overlay mode.
   1129 	   */
   1130 	if ((field->overlay == 0)
   1131 	    || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
   1132 		field->buffers[0].length++;
   1133 		bump_lines(field, (int) pos, 1);
   1134 	}
   1135 
   1136 
   1137 	  /* wrap the field, if needed */
   1138 	status = _formi_wrap_field(field, pos);
   1139 	if (status != E_OK) {
   1140 		  /* wrap failed for some reason, back out the char insert */
   1141 		bcopy(&field->buffers[0].string[pos + 1],
   1142 		      &field->buffers[0].string[pos],
   1143 		      field->buffers[0].length - pos);
   1144 		field->buffers[0].length--;
   1145 		bump_lines(field, (int) pos, -1);
   1146 	} else {
   1147 		field->buf0_status = TRUE;
   1148 		if ((field->rows + field->nrows) == 1) {
   1149 			if ((field->cursor_xpos < (field->cols - 1)) ||
   1150 			    ((field->opts & O_STATIC) != O_STATIC))
   1151 				field->cursor_xpos++;
   1152 
   1153 			if (field->cursor_xpos > field->cols) {
   1154 				field->start_char++;
   1155 				field->cursor_xpos = field->cols;
   1156 			}
   1157 		} else {
   1158 			new_size = find_cur_line(field, pos);
   1159 			if (new_size >= field->rows) {
   1160 				field->cursor_ypos = field->rows - 1;
   1161 				field->start_line = field->row_count
   1162 					- field->cursor_ypos - 1;
   1163 			} else
   1164 				field->cursor_ypos = new_size;
   1165 
   1166 			field->cursor_xpos = pos
   1167 				- field->lines[field->cursor_ypos
   1168 					      + field->start_line].start + 1;
   1169 
   1170 			  /*
   1171 			   * Annoying corner case - if we are right in
   1172 			   * the bottom right corner of the field we
   1173 			   * need to scroll the field one line so the
   1174 			   * cursor is positioned correctly in the
   1175 			   * field.
   1176 			   */
   1177 			if ((field->cursor_xpos >= field->cols) &&
   1178 			    (field->cursor_ypos == (field->rows - 1))) {
   1179 				field->cursor_ypos--;
   1180 				field->start_line++;
   1181 			}
   1182 		}
   1183 	}
   1184 
   1185 #ifdef DEBUG
   1186 	assert((field->cursor_xpos < 400000)
   1187 	       && (field->cursor_ypos < 400000)
   1188 	       && (field->start_line < 400000));
   1189 
   1190 	fprintf(dbg,
   1191 	    "add_char exit: xpos=%d, start=%d, length=%d(%d), allocated=%d\n",
   1192 		field->cursor_xpos, field->start_char,
   1193 		field->buffers[0].length, strlen(field->buffers[0].string),
   1194 		field->buffers[0].allocated);
   1195 	fprintf(dbg, "add_char exit: ypos=%d, start_line=%d\n",
   1196 		field->cursor_ypos, field->start_line);
   1197 	fprintf(dbg,"add_char exit: %s\n", field->buffers[0].string);
   1198 	fprintf(dbg, "add_char exit: buf0_status=%d\n", field->buf0_status);
   1199 	fprintf(dbg, "add_char exit: status = %s\n",
   1200 		(status == E_OK)? "OK" : "FAILED");
   1201 #endif
   1202 	return status;
   1203 }
   1204 
   1205 /*
   1206  * Manipulate the text in a field, this takes the given form and performs
   1207  * the passed driver command on the current text field.  Returns 1 if the
   1208  * text field was modified.
   1209  */
   1210 int
   1211 _formi_manipulate_field(FORM *form, int c)
   1212 {
   1213 	FIELD *cur;
   1214 	char *str;
   1215 	unsigned int i, start, end, pos, row, len, status;
   1216 
   1217 	cur = form->fields[form->cur_field];
   1218 
   1219 #ifdef DEBUG
   1220 	fprintf(dbg,
   1221 		"entry: xpos=%d, start_char=%d, length=%d, allocated=%d\n",
   1222 		cur->cursor_xpos, cur->start_char, cur->buffers[0].length,
   1223 		cur->buffers[0].allocated);
   1224 	fprintf(dbg, "entry: start_line=%d, ypos=%d\n", cur->start_line,
   1225 		cur->cursor_ypos);
   1226 	fprintf(dbg, "entry: string=");
   1227 	if (cur->buffers[0].string == NULL)
   1228 		fprintf(dbg, "(null)\n");
   1229 	else
   1230 		fprintf(dbg, "\"%s\"\n", cur->buffers[0].string);
   1231 #endif
   1232 
   1233 	  /* Cannot manipulate a null string! */
   1234 	if (cur->buffers[0].string == NULL)
   1235 		return E_REQUEST_DENIED;
   1236 
   1237 	switch (c) {
   1238 	case REQ_NEXT_CHAR:
   1239 		  /* for a dynamic field allow an offset of one more
   1240 		   * char so we can insert chars after end of string.
   1241 		   * Static fields cannot do this so deny request if
   1242 		   * cursor is at the end of the field.
   1243 		   */
   1244 		if (((cur->opts & O_STATIC) == O_STATIC) &&
   1245 		    (cur->cursor_xpos == cur->cols - 1) &&
   1246 		    ((cur->rows + cur->nrows) == 1))
   1247 			return E_REQUEST_DENIED;
   1248 
   1249 		if ((cur->cursor_xpos + cur->start_char + 1)
   1250 		    > cur->buffers[0].length)
   1251 			return E_REQUEST_DENIED;
   1252 
   1253 		if ((cur->rows + cur->nrows) == 1) {
   1254 			cur->cursor_xpos++;
   1255 			if (cur->cursor_xpos >= cur->cols - 1) {
   1256 				cur->cursor_xpos = cur->cols - 1;
   1257 				if ((cur->opts & O_STATIC) != O_STATIC)
   1258 					cur->start_char++;
   1259 			}
   1260 		} else {
   1261 			row = cur->start_line + cur->cursor_ypos;
   1262 			if (cur->cursor_xpos == (cur->lines[row].length - 1)) {
   1263 				if ((row + 1) >= cur->row_count)
   1264 					return E_REQUEST_DENIED;
   1265 
   1266 				cur->cursor_xpos = 0;
   1267 				if (cur->cursor_ypos == (cur->rows - 1))
   1268 					cur->start_line++;
   1269 				else
   1270 					cur->cursor_ypos++;
   1271 			} else
   1272 				cur->cursor_xpos++;
   1273 		}
   1274 
   1275 		break;
   1276 
   1277 	case REQ_PREV_CHAR:
   1278 		if ((cur->rows + cur->nrows) == 1) {
   1279 			if (cur->cursor_xpos == 0) {
   1280 				if (cur->start_char > 0)
   1281 					cur->start_char--;
   1282 				else
   1283 					return E_REQUEST_DENIED;
   1284 			} else
   1285 				cur->cursor_xpos--;
   1286 		} else {
   1287 			if ((cur->cursor_xpos == 0) &&
   1288 			    (cur->cursor_ypos == 0) &&
   1289 			    (cur->start_line == 0))
   1290 				return E_REQUEST_DENIED;
   1291 
   1292 			if (cur->cursor_xpos > 0) {
   1293 				cur->cursor_xpos--;
   1294 			} else {
   1295 				if (cur->cursor_ypos > 0)
   1296 					cur->cursor_ypos--;
   1297 				else
   1298 					cur->start_line--;
   1299 				cur->cursor_xpos =
   1300 					cur->lines[cur->start_line
   1301 						  + cur->cursor_ypos].length
   1302 					- 1;
   1303 			}
   1304 		}
   1305 
   1306 		break;
   1307 
   1308 	case REQ_NEXT_LINE:
   1309 		if ((cur->start_line + cur->cursor_ypos + 1) >= cur->row_count)
   1310 			return E_REQUEST_DENIED;
   1311 
   1312 		if ((cur->cursor_ypos + 1) >= cur->rows) {
   1313 			cur->start_line++;
   1314 		} else
   1315 			cur->cursor_ypos++;
   1316 		break;
   1317 
   1318 	case REQ_PREV_LINE:
   1319 		if (cur->cursor_ypos == 0) {
   1320 			if (cur->start_line == 0)
   1321 				return E_REQUEST_DENIED;
   1322 			cur->start_line--;
   1323 		} else
   1324 			cur->cursor_ypos--;
   1325 		break;
   1326 
   1327 	case REQ_NEXT_WORD:
   1328 		start = cur->lines[cur->start_line + cur->cursor_ypos].start
   1329 			+ cur->cursor_xpos + cur->start_char;
   1330 		str = cur->buffers[0].string;
   1331 
   1332 		start = find_eow(str, start);
   1333 
   1334 		  /* check if we hit the end */
   1335 		if (str[start] == '\0')
   1336 			return E_REQUEST_DENIED;
   1337 
   1338 		  /* otherwise we must have found the start of a word...*/
   1339 		if ((cur->rows + cur->nrows) == 1) {
   1340 			  /* single line field */
   1341 			if (start - cur->start_char < cur->cols) {
   1342 				cur->cursor_xpos = start;
   1343 			} else {
   1344 				cur->start_char = start;
   1345 				cur->cursor_xpos = 0;
   1346 			}
   1347 		} else {
   1348 			  /* multiline field */
   1349 			row = find_cur_line(cur, start);
   1350 			cur->cursor_xpos = start - cur->lines[row].start;
   1351 			if (row != (cur->start_line + cur->cursor_ypos)) {
   1352 				if (cur->cursor_ypos == (cur->rows - 1)) {
   1353 					cur->start_line = row - cur->rows + 1;
   1354 				} else {
   1355 					cur->cursor_ypos = row
   1356 						- cur->start_line;
   1357 				}
   1358 			}
   1359 		}
   1360 		break;
   1361 
   1362 	case REQ_PREV_WORD:
   1363 		start = cur->start_char + cur->cursor_xpos
   1364 			+ cur->lines[cur->start_line + cur->cursor_ypos].start;
   1365 		if (cur->start_char > 0)
   1366 			start--;
   1367 
   1368 		if (start == 0)
   1369 			return E_REQUEST_DENIED;
   1370 
   1371 		str = cur->buffers[0].string;
   1372 
   1373 		start = find_sow(str, start);
   1374 
   1375 		if ((cur->rows + cur->nrows) == 1) {
   1376 			  /* single line field */
   1377 			if (start - cur->start_char > 0) {
   1378 				cur->cursor_xpos = start;
   1379 			} else {
   1380 				cur->start_char = start;
   1381 				cur->cursor_xpos = 0;
   1382 			}
   1383 		} else {
   1384 			  /* multiline field */
   1385 			row = find_cur_line(cur, start);
   1386 			cur->cursor_xpos = start - cur->lines[row].start;
   1387 			if (row != (cur->start_line + cur->cursor_ypos)) {
   1388 				if (cur->cursor_ypos == 0) {
   1389 					cur->start_line = row;
   1390 				} else {
   1391 					if (cur->start_line > row) {
   1392 						cur->start_line = row;
   1393 						cur->cursor_ypos = 0;
   1394 					} else {
   1395 						cur->cursor_ypos = row -
   1396 							cur->start_line;
   1397 					}
   1398 				}
   1399 			}
   1400 		}
   1401 
   1402 		break;
   1403 
   1404 	case REQ_BEG_FIELD:
   1405 		cur->start_char = 0;
   1406 		cur->start_line = 0;
   1407 		cur->cursor_xpos = 0;
   1408 		cur->cursor_ypos = 0;
   1409 		break;
   1410 
   1411 	case REQ_BEG_LINE:
   1412 		cur->cursor_xpos = 0;
   1413 		break;
   1414 
   1415 	case REQ_END_FIELD:
   1416 		if (cur->row_count > cur->rows) {
   1417 			cur->start_line = cur->row_count - cur->rows;
   1418 			cur->cursor_ypos = cur->rows - 1;
   1419 		} else {
   1420 			cur->start_line = 0;
   1421 			cur->cursor_ypos = cur->row_count - 1;
   1422 		}
   1423 
   1424 		  /* we fall through here deliberately, we are on the
   1425 		   * correct row, now we need to get to the end of the
   1426 		   * line.
   1427 		   */
   1428 		  /* FALLTHRU */
   1429 
   1430 	case REQ_END_LINE:
   1431 		start = cur->lines[cur->start_line + cur->cursor_ypos].start;
   1432 		end = cur->lines[cur->start_line + cur->cursor_ypos].end;
   1433 
   1434 		if ((cur->rows + cur->nrows) == 1) {
   1435 			if (end - start > cur->cols - 1) {
   1436 				cur->cursor_xpos = cur->cols - 1;
   1437 				cur->start_char = end - cur->cols;
   1438 				if ((cur->opts & O_STATIC) != O_STATIC)
   1439 					cur->start_char++;
   1440 			} else {
   1441 				cur->cursor_xpos = end - start + 1;
   1442 				if (((cur->opts & O_STATIC) == O_STATIC) &&
   1443 				    ((end - start) == (cur->cols - 1)))
   1444 					cur->cursor_xpos--;
   1445 
   1446 				cur->start_char = start;
   1447 			}
   1448 		} else {
   1449 			cur->cursor_xpos = end - start + 1;
   1450 		}
   1451 		break;
   1452 
   1453 	case REQ_LEFT_CHAR:
   1454 		if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
   1455 		    && (cur->start_line == 0) && (cur->cursor_ypos == 0))
   1456 			return E_REQUEST_DENIED;
   1457 
   1458 		if (cur->cursor_xpos == 0) {
   1459 			if ((cur->rows + cur->nrows) == 1) {
   1460 				if (cur->start_char > 0)
   1461 					cur->start_char--;
   1462 				else
   1463 					return E_REQUEST_DENIED;
   1464 			} else {
   1465 				if ((cur->cursor_ypos == 0) &&
   1466 				    (cur->start_line == 0))
   1467 					return E_REQUEST_DENIED;
   1468 
   1469 				if (cur->cursor_ypos == 0)
   1470 					cur->start_line--;
   1471 				else
   1472 					cur->cursor_ypos--;
   1473 
   1474 				cur->cursor_xpos =
   1475 					cur->lines[cur->cursor_ypos
   1476 						  + cur->start_line].length;
   1477 			}
   1478 		} else
   1479 			cur->cursor_xpos--;
   1480 		break;
   1481 
   1482 	case REQ_RIGHT_CHAR:
   1483 		pos = cur->start_char + cur->cursor_xpos;
   1484 		row = cur->start_line + cur->cursor_ypos;
   1485 		end = cur->lines[row].end;
   1486 
   1487 		if (cur->buffers[0].string[pos] == '\0')
   1488 			return E_REQUEST_DENIED;
   1489 
   1490 #ifdef DEBUG
   1491 		fprintf(dbg, "req_right_char enter: start=%d, xpos=%d, c=%c\n",
   1492 			cur->start_char, cur->cursor_xpos,
   1493 			cur->buffers[0].string[pos]);
   1494 #endif
   1495 
   1496 		if (pos == end) {
   1497 			start = pos + 1;
   1498 			if ((cur->buffers[0].length <= start)
   1499 			    || ((row + 1) >= cur->row_count))
   1500 				return E_REQUEST_DENIED;
   1501 
   1502 			if ((cur->cursor_ypos + 1) >= cur->rows) {
   1503 				cur->start_line++;
   1504 				cur->cursor_ypos = cur->rows - 1;
   1505 			} else
   1506 				cur->cursor_ypos++;
   1507 
   1508 			cur->cursor_xpos = 0;
   1509 		} else {
   1510 			if (((cur->rows + cur->nrows) == 1) &&
   1511 			    (cur->cursor_xpos == cur->cols - 1))
   1512 				cur->start_char++;
   1513 			else
   1514 				cur->cursor_xpos++;
   1515 		}
   1516 #ifdef DEBUG
   1517 		fprintf(dbg, "req_right_char exit: start=%d, xpos=%d, c=%c\n",
   1518 			cur->start_char, cur->cursor_xpos,
   1519 			cur->buffers[0].string[cur->start_char +
   1520 					      cur->cursor_xpos]);
   1521 #endif
   1522 		break;
   1523 
   1524 	case REQ_UP_CHAR:
   1525 		if (cur->cursor_ypos == 0) {
   1526 			if (cur->start_line == 0)
   1527 				return E_REQUEST_DENIED;
   1528 
   1529 			cur->start_line--;
   1530 		} else
   1531 			cur->cursor_ypos--;
   1532 
   1533 		row = cur->start_line + cur->cursor_ypos;
   1534 
   1535 		if (cur->cursor_xpos > cur->lines[row].length)
   1536 			cur->cursor_xpos = cur->lines[row].length;
   1537 		break;
   1538 
   1539 	case REQ_DOWN_CHAR:
   1540 		if (cur->cursor_ypos == cur->rows - 1) {
   1541 			if (cur->start_line + cur->rows == cur->row_count)
   1542 				return E_REQUEST_DENIED;
   1543 			cur->start_line++;
   1544 		} else
   1545 			cur->cursor_ypos++;
   1546 
   1547 		row = cur->start_line + cur->cursor_ypos;
   1548 		if (cur->cursor_xpos > cur->lines[row].length)
   1549 			cur->cursor_xpos = cur->lines[row].length;
   1550 		break;
   1551 
   1552 	case REQ_NEW_LINE:
   1553 		if ((status = split_line(cur,
   1554 				cur->start_char + cur->cursor_xpos)) != E_OK)
   1555 			return status;
   1556 		break;
   1557 
   1558 	case REQ_INS_CHAR:
   1559 		_formi_add_char(cur, cur->start_char + cur->cursor_xpos,
   1560 				cur->pad);
   1561 		break;
   1562 
   1563 	case REQ_INS_LINE:
   1564 		start = cur->lines[cur->start_line + cur->cursor_ypos].start;
   1565 		if ((status = split_line(cur, start)) != E_OK)
   1566 			return status;
   1567 		break;
   1568 
   1569 	case REQ_DEL_CHAR:
   1570 		if (cur->buffers[0].length == 0)
   1571 			return E_REQUEST_DENIED;
   1572 
   1573 		row = cur->start_line + cur->cursor_ypos;
   1574 		start = cur->start_char + cur->cursor_xpos;
   1575 		end = cur->buffers[0].length;
   1576 		if (start == cur->lines[row].start) {
   1577 			if (cur->row_count > 1) {
   1578 				if (_formi_join_line(cur,
   1579 						start, JOIN_NEXT) != E_OK) {
   1580 					return E_REQUEST_DENIED;
   1581 				}
   1582 			} else {
   1583 				cur->buffers[0].string[start] = '\0';
   1584 				if (cur->lines[row].end > 0) {
   1585 					cur->lines[row].end--;
   1586 					cur->lines[row].length--;
   1587 				}
   1588 			}
   1589 		} else {
   1590 			bcopy(&cur->buffers[0].string[start + 1],
   1591 			      &cur->buffers[0].string[start],
   1592 			      (unsigned) end - start + 1);
   1593 			bump_lines(cur, _FORMI_USE_CURRENT, -1);
   1594 		}
   1595 
   1596 		cur->buffers[0].length--;
   1597 		break;
   1598 
   1599 	case REQ_DEL_PREV:
   1600 		if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
   1601 		    && (cur->start_line == 0) && (cur->cursor_ypos == 0))
   1602 			   return E_REQUEST_DENIED;
   1603 
   1604 		row = cur->start_line + cur->cursor_ypos;
   1605 		start = cur->cursor_xpos + cur->start_char
   1606 			+ cur->lines[row].start;
   1607 		end = cur->buffers[0].length;
   1608 
   1609 		if ((cur->start_char + cur->cursor_xpos) == 0) {
   1610 			if (_formi_join_line(cur, cur->lines[row].start,
   1611 					     JOIN_PREV_NW) != E_OK) {
   1612 				return E_REQUEST_DENIED;
   1613 			}
   1614 		}
   1615 
   1616 		bcopy(&cur->buffers[0].string[start],
   1617 		      &cur->buffers[0].string[start - 1],
   1618 		      (unsigned) end - start + 1);
   1619 		bump_lines(cur, start - 1, -1);
   1620 		cur->buffers[0].length--;
   1621 
   1622 		if ((cur->rows + cur->nrows) == 1) {
   1623 			if ((cur->cursor_xpos == 0) && (cur->start_char > 0))
   1624 				cur->start_char--;
   1625 			else if ((cur->cursor_xpos == cur->cols - 1)
   1626 				 && (cur->start_char > 0))
   1627 				cur->start_char--;
   1628 			else if (cur->cursor_xpos > 0)
   1629 				cur->cursor_xpos--;
   1630 		} else {
   1631 			pos = start - 1;
   1632 			if (pos >= cur->buffers[0].length)
   1633 				pos = cur->buffers[0].length - 1;
   1634 
   1635 			if ((_formi_wrap_field(cur, pos) != E_OK)) {
   1636 				/* XXX back out char deletion here */
   1637 				return E_REQUEST_DENIED;
   1638 			}
   1639 
   1640 			row = find_cur_line(cur, pos);
   1641 			cur->cursor_xpos = start - cur->lines[row].start - 1;
   1642 
   1643 			if (row >= cur->rows)
   1644 				cur->start_line = row - cur->cursor_ypos;
   1645 			else {
   1646 				cur->start_line = 0;
   1647 				cur->cursor_ypos = row;
   1648 			}
   1649 		}
   1650 		break;
   1651 
   1652 	case REQ_DEL_LINE:
   1653 		row = cur->start_line + cur->cursor_ypos;
   1654 		start = cur->lines[row].start;
   1655 		end = cur->lines[row].end;
   1656 		bcopy(&cur->buffers[0].string[end + 1],
   1657 		      &cur->buffers[0].string[start],
   1658 		      (unsigned) cur->buffers[0].length - end + 1);
   1659 /* XXXX wrong */
   1660 		if (cur->row_count > 1) {
   1661 			cur->row_count--;
   1662 			bcopy(&cur->lines[row + 1], &cur->lines[row],
   1663 			      sizeof(struct _formi_field_lines)
   1664 			      * cur->row_count);
   1665 
   1666 			len = end - start;
   1667 			for (i = row; i < cur->row_count; i++) {
   1668 				cur->lines[i].start -= len;
   1669 				cur->lines[i].end -= len;
   1670 			}
   1671 
   1672 			if (row > cur->row_count) {
   1673 				if (cur->cursor_ypos == 0) {
   1674 					if (cur->start_line > 0) {
   1675 						cur->start_line--;
   1676 					}
   1677 				} else {
   1678 					cur->cursor_ypos--;
   1679 				}
   1680 				row--;
   1681 			}
   1682 
   1683 			if (cur->cursor_xpos > cur->lines[row].length)
   1684 				cur->cursor_xpos = cur->lines[row].length;
   1685 		}
   1686 		break;
   1687 
   1688 	case REQ_DEL_WORD:
   1689 		start = cur->start_char + cur->cursor_xpos;
   1690 		end = find_eow(cur->buffers[0].string, start);
   1691 		start = find_sow(cur->buffers[0].string, start);
   1692 		bcopy(&cur->buffers[0].string[end + 1],
   1693 		      &cur->buffers[0].string[start],
   1694 		      (unsigned) cur->buffers[0].length - end + 1);
   1695 		len = end - start;
   1696 		cur->buffers[0].length -= len;
   1697 		bump_lines(cur, _FORMI_USE_CURRENT, - (int) len);
   1698 
   1699 		if (cur->cursor_xpos > cur->lines[row].length)
   1700 			cur->cursor_xpos = cur->lines[row].length;
   1701 		break;
   1702 
   1703 	case REQ_CLR_EOL:
   1704 		row = cur->start_line + cur->cursor_ypos;
   1705 		start = cur->start_char + cur->cursor_xpos;
   1706 		end = cur->lines[row].end;
   1707 		len = end - start;
   1708 		bcopy(&cur->buffers[0].string[end + 1],
   1709 		      &cur->buffers[0].string[start],
   1710 		      cur->buffers[0].length - end + 1);
   1711 		cur->buffers[0].length -= len;
   1712 		bump_lines(cur, _FORMI_USE_CURRENT, - (int) len);
   1713 
   1714 		if (cur->cursor_xpos > cur->lines[row].length)
   1715 			cur->cursor_xpos = cur->lines[row].length;
   1716 		break;
   1717 
   1718 	case REQ_CLR_EOF:
   1719 		row = cur->start_line + cur->cursor_ypos;
   1720 		cur->buffers[0].string[cur->start_char
   1721 				      + cur->cursor_xpos] = '\0';
   1722 		cur->buffers[0].length = strlen(cur->buffers[0].string);
   1723 		cur->lines[row].end = cur->buffers[0].length;
   1724 		cur->lines[row].length = cur->lines[row].end
   1725 			- cur->lines[row].start;
   1726 
   1727 		for (i = cur->start_char + cur->cursor_xpos;
   1728 		     i < cur->buffers[0].length; i++)
   1729 			cur->buffers[0].string[i] = cur->pad;
   1730 		break;
   1731 
   1732 	case REQ_CLR_FIELD:
   1733 		cur->buffers[0].string[0] = '\0';
   1734 		cur->buffers[0].length = 0;
   1735 		cur->row_count = 1;
   1736 		cur->start_line = 0;
   1737 		cur->cursor_ypos = 0;
   1738 		cur->cursor_xpos = 0;
   1739 		cur->start_char = 0;
   1740 		cur->lines[0].start = 0;
   1741 		cur->lines[0].end = 0;
   1742 		cur->lines[0].length = 0;
   1743 		break;
   1744 
   1745 	case REQ_OVL_MODE:
   1746 		cur->overlay = 1;
   1747 		break;
   1748 
   1749 	case REQ_INS_MODE:
   1750 		cur->overlay = 0;
   1751 		break;
   1752 
   1753 	case REQ_SCR_FLINE:
   1754 		_formi_scroll_fwd(cur, 1);
   1755 		break;
   1756 
   1757 	case REQ_SCR_BLINE:
   1758 		_formi_scroll_back(cur, 1);
   1759 		break;
   1760 
   1761 	case REQ_SCR_FPAGE:
   1762 		_formi_scroll_fwd(cur, cur->rows);
   1763 		break;
   1764 
   1765 	case REQ_SCR_BPAGE:
   1766 		_formi_scroll_back(cur, cur->rows);
   1767 		break;
   1768 
   1769 	case REQ_SCR_FHPAGE:
   1770 		_formi_scroll_fwd(cur, cur->rows / 2);
   1771 		break;
   1772 
   1773 	case REQ_SCR_BHPAGE:
   1774 		_formi_scroll_back(cur, cur->rows / 2);
   1775 		break;
   1776 
   1777 	case REQ_SCR_FCHAR:
   1778 		_formi_hscroll_fwd(cur, 1);
   1779 		break;
   1780 
   1781 	case REQ_SCR_BCHAR:
   1782 		_formi_hscroll_back(cur, 1);
   1783 		break;
   1784 
   1785 	case REQ_SCR_HFLINE:
   1786 		_formi_hscroll_fwd(cur, cur->cols);
   1787 		break;
   1788 
   1789 	case REQ_SCR_HBLINE:
   1790 		_formi_hscroll_back(cur, cur->cols);
   1791 		break;
   1792 
   1793 	case REQ_SCR_HFHALF:
   1794 		_formi_hscroll_fwd(cur, cur->cols / 2);
   1795 		break;
   1796 
   1797 	case REQ_SCR_HBHALF:
   1798 		_formi_hscroll_back(cur, cur->cols / 2);
   1799 		break;
   1800 
   1801 	default:
   1802 		return 0;
   1803 	}
   1804 
   1805 #ifdef DEBUG
   1806 	fprintf(dbg, "exit: xpos=%d, start_char=%d, length=%d, allocated=%d\n",
   1807 		cur->cursor_xpos, cur->start_char, cur->buffers[0].length,
   1808 		cur->buffers[0].allocated);
   1809 	fprintf(dbg, "exit: start_line=%d, ypos=%d\n", cur->start_line,
   1810 		cur->cursor_ypos);
   1811 	fprintf(dbg, "exit: string=\"%s\"\n", cur->buffers[0].string);
   1812 #endif
   1813 	return 1;
   1814 }
   1815 
   1816 /*
   1817  * Validate the give character by passing it to any type character
   1818  * checking routines, if they exist.
   1819  */
   1820 int
   1821 _formi_validate_char(FIELD *field, char c)
   1822 {
   1823 	int ret_val;
   1824 
   1825 	if (field->type == NULL)
   1826 		return E_OK;
   1827 
   1828 	ret_val = E_INVALID_FIELD;
   1829 	_formi_do_char_validation(field, field->type, c, &ret_val);
   1830 
   1831 	return ret_val;
   1832 }
   1833 
   1834 
   1835 /*
   1836  * Perform the validation of the character, invoke all field_type validation
   1837  * routines.  If the field is ok then update ret_val to E_OK otherwise
   1838  * ret_val is not changed.
   1839  */
   1840 static void
   1841 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
   1842 {
   1843 	if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
   1844 		_formi_do_char_validation(field, type->link->next, c, ret_val);
   1845 		_formi_do_char_validation(field, type->link->prev, c, ret_val);
   1846 	} else {
   1847 		if (type->char_check == NULL)
   1848 			*ret_val = E_OK;
   1849 		else {
   1850 			if (type->char_check((int)(unsigned char) c,
   1851 					     field->args) == TRUE)
   1852 				*ret_val = E_OK;
   1853 		}
   1854 	}
   1855 }
   1856 
   1857 /*
   1858  * Validate the current field.  If the field validation returns success then
   1859  * return E_OK otherwise return E_INVALID_FIELD.
   1860  *
   1861  */
   1862 int
   1863 _formi_validate_field(FORM *form)
   1864 {
   1865 	FIELD *cur;
   1866 	char *bp;
   1867 	int ret_val, count;
   1868 
   1869 
   1870 	if ((form == NULL) || (form->fields == NULL) ||
   1871 	    (form->fields[0] == NULL))
   1872 		return E_INVALID_FIELD;
   1873 
   1874 	cur = form->fields[form->cur_field];
   1875 
   1876 	bp = cur->buffers[0].string;
   1877 	count = _formi_skip_blanks(bp, 0);
   1878 
   1879 	  /* check if we have a null field, depending on the nullok flag
   1880 	   * this may be acceptable or not....
   1881 	   */
   1882 	if (cur->buffers[0].string[count] == '\0') {
   1883 		if ((cur->opts & O_NULLOK) == O_NULLOK)
   1884 			return E_OK;
   1885 		else
   1886 			return E_INVALID_FIELD;
   1887 	}
   1888 
   1889 	  /* check if an unmodified field is ok */
   1890 	if (cur->buf0_status == 0) {
   1891 		if ((cur->opts & O_PASSOK) == O_PASSOK)
   1892 			return E_OK;
   1893 		else
   1894 			return E_INVALID_FIELD;
   1895 	}
   1896 
   1897 	  /* if there is no type then just accept the field */
   1898 	if (cur->type == NULL)
   1899 		return E_OK;
   1900 
   1901 	ret_val = E_INVALID_FIELD;
   1902 	_formi_do_validation(cur, cur->type, &ret_val);
   1903 
   1904 	return ret_val;
   1905 }
   1906 
   1907 /*
   1908  * Perform the validation of the field, invoke all field_type validation
   1909  * routines.  If the field is ok then update ret_val to E_OK otherwise
   1910  * ret_val is not changed.
   1911  */
   1912 static void
   1913 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
   1914 {
   1915 	if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
   1916 		_formi_do_validation(field, type->link->next, ret_val);
   1917 		_formi_do_validation(field, type->link->prev, ret_val);
   1918 	} else {
   1919 		if (type->field_check == NULL)
   1920 			*ret_val = E_OK;
   1921 		else {
   1922 			if (type->field_check(field, field_buffer(field, 0))
   1923 			    == TRUE)
   1924 				*ret_val = E_OK;
   1925 		}
   1926 	}
   1927 }
   1928 
   1929 /*
   1930  * Select the next/previous choice for the field, the driver command
   1931  * selecting the direction will be passed in c.  Return 1 if a choice
   1932  * selection succeeded, 0 otherwise.
   1933  */
   1934 int
   1935 _formi_field_choice(FORM *form, int c)
   1936 {
   1937 	FIELDTYPE *type;
   1938 	FIELD *field;
   1939 
   1940 	if ((form == NULL) || (form->fields == NULL) ||
   1941 	    (form->fields[0] == NULL) ||
   1942 	    (form->fields[form->cur_field]->type == NULL))
   1943 		return 0;
   1944 
   1945 	field = form->fields[form->cur_field];
   1946 	type = field->type;
   1947 
   1948 	switch (c) {
   1949 	case REQ_NEXT_CHOICE:
   1950 		if (type->next_choice == NULL)
   1951 			return 0;
   1952 		else
   1953 			return type->next_choice(field,
   1954 						 field_buffer(field, 0));
   1955 
   1956 	case REQ_PREV_CHOICE:
   1957 		if (type->prev_choice == NULL)
   1958 			return 0;
   1959 		else
   1960 			return type->prev_choice(field,
   1961 						 field_buffer(field, 0));
   1962 
   1963 	default: /* should never happen! */
   1964 		return 0;
   1965 	}
   1966 }
   1967 
   1968 /*
   1969  * Update the fields if they have changed.  The parameter old has the
   1970  * previous current field as the current field may have been updated by
   1971  * the driver.  Return 1 if the form page needs updating.
   1972  *
   1973  */
   1974 int
   1975 _formi_update_field(FORM *form, int old_field)
   1976 {
   1977 	int cur, i;
   1978 
   1979 	cur = form->cur_field;
   1980 
   1981 	if (old_field != cur) {
   1982 		if (!((cur >= form->page_starts[form->page].first) &&
   1983 		      (cur <= form->page_starts[form->page].last))) {
   1984 			  /* not on same page any more */
   1985 			for (i = 0; i < form->max_page; i++) {
   1986 				if ((form->page_starts[i].in_use == 1) &&
   1987 				    (form->page_starts[i].first <= cur) &&
   1988 				    (form->page_starts[i].last >= cur)) {
   1989 					form->page = i;
   1990 					return 1;
   1991 				}
   1992 			}
   1993 		}
   1994 	}
   1995 
   1996 	_formi_redraw_field(form, old_field);
   1997 	_formi_redraw_field(form, form->cur_field);
   1998 	return 0;
   1999 }
   2000 
   2001 /*
   2002  * Compare function for the field sorting
   2003  *
   2004  */
   2005 static int
   2006 field_sort_compare(const void *one, const void *two)
   2007 {
   2008 	const FIELD *a, *b;
   2009 	int tl;
   2010 
   2011 	  /* LINTED const castaway; we don't modify these! */
   2012 	a = (const FIELD *) *((const FIELD **) one);
   2013 	b = (const FIELD *) *((const FIELD **) two);
   2014 
   2015 	if (a == NULL)
   2016 		return 1;
   2017 
   2018 	if (b == NULL)
   2019 		return -1;
   2020 
   2021 	  /*
   2022 	   * First check the page, we want the fields sorted by page.
   2023 	   *
   2024 	   */
   2025 	if (a->page != b->page)
   2026 		return ((a->page > b->page)? 1 : -1);
   2027 
   2028 	tl = _formi_top_left(a->parent, a->index, b->index);
   2029 
   2030 	  /*
   2031 	   * sort fields left to right, top to bottom so the top left is
   2032 	   * the less than value....
   2033 	   */
   2034 	return ((tl == a->index)? -1 : 1);
   2035 }
   2036 
   2037 /*
   2038  * Sort the fields in a form ready for driver traversal.
   2039  */
   2040 void
   2041 _formi_sort_fields(FORM *form)
   2042 {
   2043 	FIELD **sort_area;
   2044 	int i;
   2045 
   2046 	CIRCLEQ_INIT(&form->sorted_fields);
   2047 
   2048 	if ((sort_area = (FIELD **) malloc(sizeof(FIELD *) * form->field_count))
   2049 	    == NULL)
   2050 		return;
   2051 
   2052 	bcopy(form->fields, sort_area, sizeof(FIELD *) * form->field_count);
   2053 	qsort(sort_area, (unsigned) form->field_count, sizeof(FIELD *),
   2054 	      field_sort_compare);
   2055 
   2056 	for (i = 0; i < form->field_count; i++)
   2057 		CIRCLEQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
   2058 
   2059 	free(sort_area);
   2060 }
   2061 
   2062 /*
   2063  * Set the neighbours for all the fields in the given form.
   2064  */
   2065 void
   2066 _formi_stitch_fields(FORM *form)
   2067 {
   2068 	int above_row, below_row, end_above, end_below, cur_row, real_end;
   2069 	FIELD *cur, *above, *below;
   2070 
   2071 	  /*
   2072 	   * check if the sorted fields circle queue is empty, just
   2073 	   * return if it is.
   2074 	   */
   2075 	if (CIRCLEQ_EMPTY(&form->sorted_fields))
   2076 		return;
   2077 
   2078 	  /* initially nothing is above..... */
   2079 	above_row = -1;
   2080 	end_above = TRUE;
   2081 	above = NULL;
   2082 
   2083 	  /* set up the first field as the current... */
   2084 	cur = CIRCLEQ_FIRST(&form->sorted_fields);
   2085 	cur_row = cur->form_row;
   2086 
   2087 	  /* find the first field on the next row if any */
   2088 	below = CIRCLEQ_NEXT(cur, glue);
   2089 	below_row = -1;
   2090 	end_below = TRUE;
   2091 	real_end = TRUE;
   2092 	while (below != (void *)&form->sorted_fields) {
   2093 		if (below->form_row != cur_row) {
   2094 			below_row = below->form_row;
   2095 			end_below = FALSE;
   2096 			real_end = FALSE;
   2097 			break;
   2098 		}
   2099 		below = CIRCLEQ_NEXT(below, glue);
   2100 	}
   2101 
   2102 	  /* walk the sorted fields, setting the neighbour pointers */
   2103 	while (cur != (void *) &form->sorted_fields) {
   2104 		if (cur == CIRCLEQ_FIRST(&form->sorted_fields))
   2105 			cur->left = NULL;
   2106 		else
   2107 			cur->left = CIRCLEQ_PREV(cur, glue);
   2108 
   2109 		if (cur == CIRCLEQ_LAST(&form->sorted_fields))
   2110 			cur->right = NULL;
   2111 		else
   2112 			cur->right = CIRCLEQ_NEXT(cur, glue);
   2113 
   2114 		if (end_above == TRUE)
   2115 			cur->up = NULL;
   2116 		else {
   2117 			cur->up = above;
   2118 			above = CIRCLEQ_NEXT(above, glue);
   2119 			if (above_row != above->form_row) {
   2120 				end_above = TRUE;
   2121 				above_row = above->form_row;
   2122 			}
   2123 		}
   2124 
   2125 		if (end_below == TRUE)
   2126 			cur->down = NULL;
   2127 		else {
   2128 			cur->down = below;
   2129 			below = CIRCLEQ_NEXT(below, glue);
   2130 			if (below == (void *) &form->sorted_fields) {
   2131 				end_below = TRUE;
   2132 				real_end = TRUE;
   2133 			} else if (below_row != below->form_row) {
   2134 				end_below = TRUE;
   2135 				below_row = below->form_row;
   2136 			}
   2137 		}
   2138 
   2139 		cur = CIRCLEQ_NEXT(cur, glue);
   2140 		if ((cur != (void *) &form->sorted_fields)
   2141 		    && (cur_row != cur->form_row)) {
   2142 			cur_row = cur->form_row;
   2143 			if (end_above == FALSE) {
   2144 				for (; above != CIRCLEQ_FIRST(&form->sorted_fields);
   2145 				     above = CIRCLEQ_NEXT(above, glue)) {
   2146 					if (above->form_row != above_row) {
   2147 						above_row = above->form_row;
   2148 						break;
   2149 					}
   2150 				}
   2151 			} else if (above == NULL) {
   2152 				above = CIRCLEQ_FIRST(&form->sorted_fields);
   2153 				end_above = FALSE;
   2154 				above_row = above->form_row;
   2155 			} else
   2156 				end_above = FALSE;
   2157 
   2158 			if (end_below == FALSE) {
   2159 				while (below_row == below->form_row) {
   2160 					below = CIRCLEQ_NEXT(below,
   2161 							     glue);
   2162 					if (below ==
   2163 					    (void *)&form->sorted_fields) {
   2164 						real_end = TRUE;
   2165 						end_below = TRUE;
   2166 						break;
   2167 					}
   2168 				}
   2169 
   2170 				if (below != (void *)&form->sorted_fields)
   2171 					below_row = below->form_row;
   2172 			} else if (real_end == FALSE)
   2173 				end_below = FALSE;
   2174 
   2175 		}
   2176 	}
   2177 }
   2178