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