1 /*        $NetBSD: internals.c,v 1.43 2023/08/01 07:56:23 mrg 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 <sys/cdefs.h>
33 __RCSID("$NetBSD: internals.c,v 1.43 2023/08/01 07:56:23 mrg Exp $");
34 
35 #include <limits.h>
36 #include <ctype.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <strings.h>
40 #include <assert.h>
41 #include <err.h>
42 #include <stdarg.h>
43 #include "internals.h"
44 #include "form.h"
45 
46 #ifdef DEBUG
47 /*
48  *  file handle to write debug info to, this will be initialised when
49  *  the form is first posted.
50  */
51 
52 /*
53  * map the request numbers to strings for debug
54  */
55 static const char *reqs[] = {
56           "NEXT_PAGE", "PREV_PAGE", "FIRST_PAGE", "LAST_PAGE", "NEXT_FIELD",
57           "PREV_FIELD", "FIRST_FIELD", "LAST_FIELD", "SNEXT_FIELD",
58           "SPREV_FIELD", "SFIRST_FIELD", "SLAST_FIELD", "LEFT_FIELD",
59           "RIGHT_FIELD", "UP_FIELD", "DOWN_FIELD", "NEXT_CHAR", "PREV_CHAR",
60           "NEXT_LINE", "PREV_LINE", "NEXT_WORD", "PREV_WORD", "BEG_FIELD",
61           "END_FIELD", "BEG_LINE", "END_LINE", "LEFT_CHAR", "RIGHT_CHAR",
62           "UP_CHAR", "DOWN_CHAR", "NEW_LINE", "INS_CHAR", "INS_LINE",
63           "DEL_CHAR", "DEL_PREV", "DEL_LINE", "DEL_WORD", "CLR_EOL",
64           "CLR_EOF", "CLR_FIELD", "OVL_MODE", "INS_MODE", "SCR_FLINE",
65           "SCR_BLINE", "SCR_FPAGE", "SCR_BPAGE", "SCR_FHPAGE", "SCR_BHPAGE",
66           "SCR_FCHAR", "SCR_BCHAR", "SCR_HFLINE", "SCR_HBLINE", "SCR_HFHALF",
67           "SCR_HBHALF", "VALIDATION", "PREV_CHOICE", "NEXT_CHOICE" };
68 #endif
69 
70 /* define our own min function - this is not generic but will do here
71  * (don't believe me?  think about what value you would get
72  * from min(x++, y++)
73  */
74 #define min(a,b) (((a) > (b))? (b) : (a))
75 
76 /* for the line joining function... */
77 #define JOIN_NEXT    1
78 #define JOIN_NEXT_NW 2 /* next join, don't wrap the joined line */
79 #define JOIN_PREV    3
80 #define JOIN_PREV_NW 4 /* previous join, don't wrap the joined line */
81 
82 /* for the bump_lines function... */
83 #define _FORMI_USE_CURRENT -1 /* indicates current cursor pos to be used */
84 
85 /* used in add_char for initial memory allocation for string in row */
86 #define INITIAL_LINE_ALLOC 16
87 
88 unsigned
89 field_skip_blanks(unsigned int start, _FORMI_FIELD_LINES **rowp);
90 static void
91 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val);
92 static void
93 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val);
94 static int
95 _formi_join_line(FIELD *field, _FORMI_FIELD_LINES **rowp, int direction);
96 void
97 _formi_hscroll_back(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt);
98 void
99 _formi_hscroll_fwd(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt);
100 static void
101 _formi_scroll_back(FIELD *field, unsigned int amt);
102 static void
103 _formi_scroll_fwd(FIELD *field, unsigned int amt);
104 static int
105 _formi_set_cursor_xpos(FIELD *field, int no_scroll);
106 static int
107 find_sow(unsigned int offset, _FORMI_FIELD_LINES **rowp);
108 static int
109 split_line(FIELD *field, bool hard_split, unsigned pos,
110              _FORMI_FIELD_LINES **rowp);
111 static bool
112 check_field_size(FIELD *field);
113 static int
114 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c);
115 static int
116 tab_size(_FORMI_FIELD_LINES *row, unsigned int i);
117 static unsigned int
118 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int len);
119 static int
120 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window);
121 static void
122 add_to_free(FIELD *field, _FORMI_FIELD_LINES *line);
123 static void
124 adjust_ypos(FIELD *field, _FORMI_FIELD_LINES *line);
125 static _FORMI_FIELD_LINES *
126 copy_row(_FORMI_FIELD_LINES *row);
127 static void
128 destroy_row_list(_FORMI_FIELD_LINES *start);
129 
130 /*
131  * Calculate the cursor y position to make the given row appear on the
132  * field.  This may be as simple as just changing the ypos (if at all) but
133  * may encompass resetting the start_line of the field to place the line
134  * at the bottom of the field.  The field is assumed to be a multi-line one.
135  */
136 static void
adjust_ypos(FIELD * field,_FORMI_FIELD_LINES * line)137 adjust_ypos(FIELD *field, _FORMI_FIELD_LINES *line)
138 {
139           unsigned ypos;
140           _FORMI_FIELD_LINES *rs;
141 
142           ypos = 0;
143           rs = field->alines;
144           while (rs != line) {
145                     rs = rs->next;
146                     ypos++;
147           }
148 
149           field->cursor_ypos = ypos;
150           field->start_line = field->alines;
151           if (ypos > (field->rows - 1)) {
152                       /*
153                        * cur_line off the end of the field,
154                        * adjust start_line so fix this.
155                        */
156                     field->cursor_ypos = field->rows - 1;
157                     ypos = ypos - (field->rows - 1);
158                     while (ypos > 0) {
159                               ypos--;
160                               field->start_line = field->start_line->next;
161                     }
162           }
163 }
164 
165 
166 /*
167  * Delete the given row and add it to the free list of the given field.
168  */
169 static void
add_to_free(FIELD * field,_FORMI_FIELD_LINES * line)170 add_to_free(FIELD *field, _FORMI_FIELD_LINES *line)
171 {
172           _FORMI_FIELD_LINES *saved;
173 
174           saved = line;
175 
176             /* don't remove if only one line... */
177           if ((line->prev == NULL) && (line->next == NULL))
178                     return;
179 
180           if (line->prev == NULL) {
181                     /* handle top of list */
182                     field->alines = line->next;
183                     field->alines->prev = NULL;
184 
185                     if (field->cur_line == saved)
186                               field->cur_line = field->alines;
187                     if (field->start_line == saved)
188                               field->start_line = saved;
189           } else if (line->next == NULL) {
190                     /* handle bottom of list */
191                     line->prev->next = NULL;
192                     if (field->cur_line == saved)
193                               field->cur_line = saved->prev;
194                     if (field->start_line == saved)
195                               field->cur_line = saved->prev;
196           } else {
197                     saved->next->prev = saved->prev;
198                     saved->prev->next = saved->next;
199                     if (field->cur_line == saved)
200                               field->cur_line = saved->prev;
201                     if (field->start_line == saved)
202                               field->start_line = saved;
203           }
204 
205           saved->next = field->free;
206           field->free = saved;
207           saved->prev = NULL;
208           if (saved->next != NULL)
209                     saved->next->prev = line;
210 }
211 
212 /*
213  * Duplicate the given row, return the pointer to the new copy or
214  * NULL if the copy fails.
215  */
216 static _FORMI_FIELD_LINES *
copy_row(_FORMI_FIELD_LINES * row)217 copy_row(_FORMI_FIELD_LINES *row)
218 {
219           _FORMI_FIELD_LINES *new;
220           _formi_tab_t *tp, *newt;
221 
222           if ((new = malloc(sizeof(*new))) == NULL) {
223                     return NULL;
224           }
225 
226           memcpy(new, row, sizeof(*new));
227 
228             /* nuke the pointers from the source row so we don't get confused */
229           new->next = NULL;
230           new->prev = NULL;
231           new->tabs = NULL;
232 
233           if ((new->string = malloc((size_t)new->allocated)) == NULL) {
234                     free(new);
235                     return NULL;
236           }
237 
238           memcpy(new->string, row->string, (size_t) row->length + 1);
239 
240           if (row->tabs != NULL) {
241                     tp = row->tabs;
242                     if ((new->tabs = malloc(sizeof(*new->tabs))) == NULL) {
243                               free(new->string);
244                               free(new);
245                               return NULL;
246                     }
247 
248                     memcpy(new->tabs, row->tabs, sizeof(*new->tabs));
249                     new->tabs->back = NULL;
250                     new->tabs->fwd = NULL;
251 
252                     tp = tp->fwd;
253                     newt = new->tabs;
254 
255                     while (tp != NULL) {
256                               if ((newt->fwd = malloc(sizeof(*newt->fwd))) == NULL) {
257                                         /* error... unwind allocations */
258                                         tp = new->tabs;
259                                         while (tp != NULL) {
260                                                   newt = tp->fwd;
261                                                   free(tp);
262                                                   tp = newt;
263                                         }
264 
265                                         free(new->string);
266                                         free(new);
267                                         return NULL;
268                               }
269 
270                               memcpy(newt->fwd, tp, sizeof(*newt->fwd));
271                               newt->fwd->back = newt;
272                               newt = newt->fwd;
273                               newt->fwd = NULL;
274                               tp = tp->fwd;
275                     }
276           }
277 
278           return new;
279 }
280 
281 /*
282  * Initialise the row offset for a field, depending on the type of
283  * field it is and the type of justification used.  The justification
284  * is only used on static single line fields, everything else will
285  * have the cursor_xpos set to 0.
286  */
287 void
_formi_init_field_xpos(FIELD * field)288 _formi_init_field_xpos(FIELD *field)
289 {
290             /* not static or is multi-line which are not justified, so 0 it is */
291           if (((field->opts & O_STATIC) != O_STATIC) ||
292               ((field->rows + field->nrows) != 1)) {
293                     field->cursor_xpos = 0;
294                     return;
295           }
296 
297           switch (field->justification) {
298           case JUSTIFY_RIGHT:
299                     field->cursor_xpos = field->cols - 1;
300                     break;
301 
302           case JUSTIFY_CENTER:
303                     field->cursor_xpos = (field->cols - 1) / 2;
304                     break;
305 
306           default: /* assume left justify */
307                     field->cursor_xpos = 0;
308                     break;
309           }
310 }
311 
312 
313 /*
314  * Open the debug file if it is not already open....
315  */
316 #ifdef DEBUG
317 static FILE *dbg;
318 static const char dbg_file[] = "___form_dbg.out";
319 
320 void
_formi_dbg_printf(const char * fmt,...)321 _formi_dbg_printf(const char *fmt, ...)
322 {
323           va_list ap;
324 
325           if (dbg == NULL && (dbg = fopen(dbg_file, "w")) == NULL) {
326                     warn("Cannot open debug file `%s'", dbg_file);
327                     return;
328           }
329           va_start(ap, fmt);
330           vfprintf(dbg, fmt, ap);
331           va_end(ap);
332 }
333 #endif
334 
335 /*
336  * Check the sizing of the field, if the maximum size is set for a
337  * dynamic field then check that the number of rows or columns does
338  * not exceed the set maximum.  The decision to check the rows or
339  * columns is made on the basis of how many rows are in the field -
340  * one row means the max applies to the number of columns otherwise it
341  * applies to the number of rows.  If the row/column count is less
342  * than the maximum then return TRUE.
343  *
344  */
345 static bool
check_field_size(FIELD * field)346 check_field_size(FIELD *field)
347 {
348           if ((field->opts & O_STATIC) != O_STATIC) {
349                       /* dynamic field */
350                     if (field->max == 0) /* unlimited */
351                               return TRUE;
352 
353                     if (field->rows == 1) {
354                               return (field->alines->length < field->max);
355                     } else {
356                               return (field->row_count <= field->max);
357                     }
358           } else {
359                     if ((field->rows + field->nrows) == 1) {
360                               return (field->alines->length <= field->cols);
361                     } else {
362                               return (field->row_count <= (field->rows
363                                                                  + field->nrows));
364                     }
365           }
366 }
367 
368 /*
369  * Set the form's current field to the first valid field on the page.
370  * Assume the fields have been sorted and stitched.
371  */
372 int
_formi_pos_first_field(FORM * form)373 _formi_pos_first_field(FORM *form)
374 {
375           FIELD *cur;
376           int old_page;
377 
378           old_page = form->page;
379 
380             /* scan forward for an active page....*/
381           while (form->page_starts[form->page].in_use == 0) {
382                     form->page++;
383                     if (form->page > form->max_page) {
384                               form->page = old_page;
385                               return E_REQUEST_DENIED;
386                     }
387           }
388 
389             /* then scan for a field we can use */
390           cur = form->fields[form->page_starts[form->page].first];
391           while ((cur->opts & (O_VISIBLE | O_ACTIVE))
392                  != (O_VISIBLE | O_ACTIVE)) {
393                     cur = TAILQ_NEXT(cur, glue);
394                     if (cur == NULL) {
395                               form->page = old_page;
396                               return E_REQUEST_DENIED;
397                     }
398           }
399 
400           form->cur_field = cur->index;
401           return E_OK;
402 }
403 
404 /*
405  * Set the field to the next active and visible field, the fields are
406  * traversed in index order in the direction given.  If the parameter
407  * use_sorted is TRUE then the sorted field list will be traversed instead
408  * of using the field index.
409  */
410 int
_formi_pos_new_field(FORM * form,unsigned direction,unsigned use_sorted)411 _formi_pos_new_field(FORM *form, unsigned direction, unsigned use_sorted)
412 {
413           FIELD *cur;
414           int i;
415 
416           i = form->cur_field;
417           cur = form->fields[i];
418 
419           do {
420                     if (direction == _FORMI_FORWARD) {
421                               if (use_sorted == TRUE) {
422                                         if ((form->wrap == FALSE) &&
423                                             (cur == TAILQ_LAST(&form->sorted_fields,
424                                                   _formi_sort_head)))
425                                                   return E_REQUEST_DENIED;
426                                         cur = TAILQ_NEXT(cur, glue);
427                                         i = cur->index;
428                               } else {
429                                         if ((form->wrap == FALSE) &&
430                                             ((i + 1) >= form->field_count))
431                                                   return E_REQUEST_DENIED;
432                                         i++;
433                                         if (i >= form->field_count)
434                                                   i = 0;
435                               }
436                     } else {
437                               if (use_sorted == TRUE) {
438                                         if ((form->wrap == FALSE) &&
439                                             (cur == TAILQ_FIRST(&form->sorted_fields)))
440                                                   return E_REQUEST_DENIED;
441                                         cur = TAILQ_PREV(cur, _formi_sort_head, glue);
442                                         i = cur->index;
443                               } else {
444                                         if ((form->wrap == FALSE) && (i <= 0))
445                                                   return E_REQUEST_DENIED;
446                                         i--;
447                                         if (i < 0)
448                                                   i = form->field_count - 1;
449                               }
450                     }
451 
452                     if ((form->fields[i]->opts & (O_VISIBLE | O_ACTIVE))
453                               == (O_VISIBLE | O_ACTIVE)) {
454                               form->cur_field = i;
455                               return E_OK;
456                     }
457           }
458           while (i != form->cur_field);
459 
460           return E_REQUEST_DENIED;
461 }
462 
463 /*
464  * Destroy the list of line structs passed by freeing all allocated
465  * memory.
466  */
467 static void
destroy_row_list(_FORMI_FIELD_LINES * start)468 destroy_row_list(_FORMI_FIELD_LINES *start)
469 {
470           _FORMI_FIELD_LINES *temp, *row;
471           _formi_tab_t *tt, *tp;
472 
473           row = start;
474           while (row != NULL) {
475                     if (row->tabs != NULL) {
476                                 /* free up the tab linked list... */
477                               tp = row->tabs;
478                               while (tp != NULL) {
479                                         tt = tp->fwd;
480                                         free(tp);
481                                         tp = tt;
482                               }
483                     }
484 
485                     if (row->string != NULL)
486                               free(row->string);
487 
488                     temp = row->next;
489                     free(row);
490                     row = temp;
491           }
492 }
493 
494 /*
495  * Word wrap the contents of the field's buffer 0 if this is allowed.
496  * If the wrap is successful, that is, the row count nor the buffer
497  * size is exceeded then the function will return E_OK, otherwise it
498  * will return E_REQUEST_DENIED.
499  */
500 int
_formi_wrap_field(FIELD * field,_FORMI_FIELD_LINES * loc)501 _formi_wrap_field(FIELD *field, _FORMI_FIELD_LINES *loc)
502 {
503           int width, wrap_err;
504           unsigned int pos, saved_xpos, saved_ypos, saved_cur_xpos;
505           unsigned int saved_row_count;
506           _FORMI_FIELD_LINES *saved_row, *row, *row_backup, *saved_cur_line;
507           _FORMI_FIELD_LINES *saved_start_line, *temp;
508 
509           if ((field->opts & O_STATIC) == O_STATIC) {
510                     if ((field->rows + field->nrows) == 1) {
511                               return E_OK; /* cannot wrap a single line */
512                     }
513                     width = field->cols;
514           } else {
515                       /* if we are limited to one line then don't try to wrap */
516                     if ((field->drows + field->nrows) == 1) {
517                               return E_OK;
518                     }
519 
520                       /*
521                        * hueristic - if a dynamic field has more than one line
522                        * on the screen then the field grows rows, otherwise
523                        * it grows columns, effectively a single line field.
524                        * This is documented AT&T behaviour.
525                        */
526                     if (field->rows > 1) {
527                               width = field->cols;
528                     } else {
529                               return E_OK;
530                     }
531           }
532 
533           row = loc;
534 
535             /* if we are not at the top of the field then back up one
536              * row because we may be able to merge the current row into
537              * the one above.
538              */
539           if (row->prev != NULL)
540                     row = row->prev;
541 
542           saved_row = row;
543           saved_xpos = field->row_xpos;
544           saved_cur_xpos = field->cursor_xpos;
545           saved_ypos = field->cursor_ypos;
546           saved_row_count = field->row_count;
547 
548             /*
549              * Save a copy of the lines affected, just in case things
550              * don't work out.
551              */
552           if ((row_backup = copy_row(row)) == NULL)
553                     return E_SYSTEM_ERROR;
554 
555           temp = row_backup;
556           row = row->next;
557 
558           saved_cur_line = temp;
559           saved_start_line = temp;
560 
561           while (row != NULL) {
562                     if ((temp->next = copy_row(row)) == NULL) {
563                                 /* a row copy failed... free up allocations */
564                               destroy_row_list(row_backup);
565                               return E_SYSTEM_ERROR;
566                     }
567 
568                     temp->next->prev = temp;
569                     temp = temp->next;
570 
571                     if (row == field->start_line)
572                               saved_start_line = temp;
573                     if (row == field->cur_line)
574                               saved_cur_line = temp;
575 
576                     row = row->next;
577           }
578 
579           row = saved_row;
580           while (row != NULL) {
581                     pos = row->length - 1;
582                     if (row->expanded < width) {
583                                 /* line may be too short, try joining some lines */
584                               if ((row->hard_ret == TRUE) && (row->next != NULL)) {
585                                         /*
586                                          * Skip the line if it has a hard return
587                                          * and it is not the last, we cannot join
588                                          * anything to it.
589                                          */
590                                         row = row->next;
591                                         continue;
592                               }
593 
594                               if (row->next == NULL) {
595                                         /*
596                                          * If there are no more lines and this line
597                                          * is too short then our job is over.
598                                          */
599                                         break;
600                               }
601 
602                               if (_formi_join_line(field, &row,
603                                                        JOIN_NEXT_NW) == E_OK) {
604                                         continue;
605                               } else
606                                         break;
607                     } else if (row->expanded > width) {
608                                 /* line is too long, split it */
609 
610                                 /*
611                                  * split on first whitespace before current word
612                                  * if the line has tabs we need to work out where
613                                  * the field border lies when the tabs are expanded.
614                                  */
615                               if (row->tabs == NULL) {
616                                         pos = width - 1;
617                                         if (pos >= row->expanded)
618                                                   pos = row->expanded - 1;
619                               } else {
620                                         pos = tab_fit_len(row, field->cols);
621                               }
622 
623                               if ((!isblank((unsigned char)row->string[pos])) &&
624                                   ((field->opts & O_WRAP) == O_WRAP)) {
625                                         if (!isblank((unsigned char)row->string[pos - 1]))
626                                                   pos = find_sow((unsigned int) pos,
627                                                                    &row);
628                                         /*
629                                          * If we cannot split the line then return
630                                          * NO_ROOM so the driver can tell that it
631                                          * should not autoskip (if that is enabled)
632                                          */
633                                         if ((pos == 0)
634                                             || (!isblank((unsigned char)row->string[pos - 1]))) {
635                                                   wrap_err = E_NO_ROOM;
636                                                   goto restore_and_exit;
637                                         }
638                               }
639 
640                                 /* if we are at the end of the string and it has
641                                  * a trailing blank, don't wrap the blank.
642                                  */
643                               if ((row->next == NULL) && (pos == row->length - 1) &&
644                                   (isblank((unsigned char)row->string[pos])) &&
645                                   row->expanded <= field->cols)
646                                         continue;
647 
648                                 /*
649                                  * otherwise, if we are still sitting on a
650                                  * blank but not at the end of the line
651                                  * move forward one char so the blank
652                                  * is on the line boundary.
653                                  */
654                               if ((isblank((unsigned char)row->string[pos])) &&
655                                   (pos != row->length - 1))
656                                         pos++;
657 
658                               if (split_line(field, FALSE, pos, &row) != E_OK) {
659                                         wrap_err = E_REQUEST_DENIED;
660                                         goto restore_and_exit;
661                               }
662                     } else
663                                 /* line is exactly the right length, do next one */
664                               row = row->next;
665           }
666 
667             /* Check if we have not run out of room */
668           if ((((field->opts & O_STATIC) == O_STATIC) &&
669                field->row_count > (field->rows + field->nrows)) ||
670               ((field->max != 0) && (field->row_count > field->max))) {
671 
672                     wrap_err = E_REQUEST_DENIED;
673 
674             restore_and_exit:
675                     if (saved_row->prev == NULL) {
676                               field->alines = row_backup;
677                     } else {
678                               saved_row->prev->next = row_backup;
679                               row_backup->prev = saved_row->prev;
680                     }
681 
682                     field->row_xpos = saved_xpos;
683                     field->cursor_xpos = saved_cur_xpos;
684                     field->cursor_ypos = saved_ypos;
685                     field->row_count = saved_row_count;
686                     field->start_line = saved_start_line;
687                     field->cur_line = saved_cur_line;
688 
689                     destroy_row_list(saved_row);
690                     return wrap_err;
691           }
692 
693           destroy_row_list(row_backup);
694           return E_OK;
695 }
696 
697 /*
698  * Join the two lines that surround the location pos, the type
699  * variable indicates the direction of the join, JOIN_NEXT will join
700  * the next line to the current line, JOIN_PREV will join the current
701  * line to the previous line, the new lines will be wrapped unless the
702  * _NW versions of the directions are used.  Returns E_OK if the join
703  * was successful or E_REQUEST_DENIED if the join cannot happen.
704  */
705 static int
_formi_join_line(FIELD * field,_FORMI_FIELD_LINES ** rowp,int direction)706 _formi_join_line(FIELD *field, _FORMI_FIELD_LINES **rowp, int direction)
707 {
708           int old_len, count;
709           struct _formi_field_lines *saved;
710           char *newp;
711           _FORMI_FIELD_LINES *row = *rowp;
712 
713           _formi_dbg_printf("%s: working on row %p, row_count = %d\n",
714               __func__, row, field->row_count);
715 
716           if ((direction == JOIN_NEXT) || (direction == JOIN_NEXT_NW)) {
717                       /*
718                        * See if there is another line following, or if the
719                        * line contains a hard return then we don't join
720                        * any lines to it.
721                        */
722                     if ((row->next == NULL) || (row->hard_ret == TRUE)) {
723                               return E_REQUEST_DENIED;
724                     }
725 
726                     _formi_dbg_printf(
727                         "%s: join_next before length = %d, expanded = %d",
728                         __func__, row->length, row->expanded);
729                     _formi_dbg_printf(
730                         " :: next row length = %d, expanded = %d\n",
731                         row->length, row->expanded);
732 
733                     if (row->allocated < (row->length + row->next->length + 1)) {
734                               if ((newp = realloc(row->string, (size_t)(row->length +
735                                                                         row->next->length
736                                                                         + 1))) == NULL)
737                                         return E_REQUEST_DENIED;
738                               row->string = newp;
739                               row->allocated = row->length + row->next->length + 1;
740                     }
741 
742                     strcat(row->string, row->next->string);
743                     old_len = row->length;
744                     row->length += row->next->length;
745                     if (row->length > 0)
746                               row->expanded =
747                                         _formi_tab_expanded_length(row->string, 0,
748                                                                          row->length - 1);
749                     else
750                               row->expanded = 0;
751 
752                     _formi_calculate_tabs(row);
753                     row->hard_ret = row->next->hard_ret;
754 
755                       /* adjust current line if it is on the row being eaten */
756                     if (field->cur_line == row->next) {
757                               field->cur_line = row;
758                               field->row_xpos += old_len;
759                               field->cursor_xpos =
760                                         _formi_tab_expanded_length(row->string, 0,
761                                                                          field->row_xpos);
762                               if (field->cursor_xpos > 0)
763                                         field->cursor_xpos--;
764 
765                               if (field->cursor_ypos > 0)
766                                         field->cursor_ypos--;
767                               else {
768                                         if (field->start_line->prev != NULL)
769                                                   field->start_line =
770                                                             field->start_line->prev;
771                               }
772                     }
773 
774                       /* remove joined line record from the row list */
775                     add_to_free(field, row->next);
776 
777                     _formi_dbg_printf(
778                         "%s: exit length = %d, expanded = %d\n",
779                         __func__, row->length, row->expanded);
780           } else {
781                     if (row->prev == NULL) {
782                               return E_REQUEST_DENIED;
783                     }
784 
785                     saved = row->prev;
786 
787                       /*
788                        * Don't try to join if the line above has a hard
789                        * return on it.
790                        */
791                     if (saved->hard_ret == TRUE) {
792                               return E_REQUEST_DENIED;
793                     }
794 
795                     _formi_dbg_printf(
796                         "%s: join_prev before length = %d, expanded = %d",
797                         __func__, row->length, row->expanded);
798                     _formi_dbg_printf(
799                         " :: prev row length = %d, expanded = %d\n",
800                         saved->length, saved->expanded);
801 
802                     if (saved->allocated < (row->length + saved->length + 1)) {
803                               if ((newp = realloc(saved->string,
804                                                       (size_t) (row->length +
805                                                                   saved->length
806                                                                   + 1))) == NULL)
807                                         return E_REQUEST_DENIED;
808                               saved->string = newp;
809                               saved->allocated = row->length + saved->length + 1;
810                     }
811 
812                     strcat(saved->string, row->string);
813                     old_len = saved->length;
814                     saved->length += row->length;
815                     if (saved->length > 0)
816                               saved->expanded =
817                                         _formi_tab_expanded_length(saved->string, 0,
818                                                                          saved->length - 1);
819                     else
820                               saved->length = 0;
821 
822                     saved->hard_ret = row->hard_ret;
823 
824                       /* adjust current line if it was on the row being eaten */
825                     if (field->cur_line == row) {
826                               field->cur_line = saved;
827                               field->row_xpos += old_len;
828                               field->cursor_xpos =
829                                         _formi_tab_expanded_length(saved->string, 0,
830                                                                          field->row_xpos);
831                               if (field->cursor_xpos > 0)
832                                         field->cursor_xpos--;
833                     }
834 
835                     add_to_free(field, row);
836 
837                     _formi_dbg_printf(
838                         "%s: exit length = %d, expanded = %d\n", __func__,
839                         saved->length, saved->expanded);
840                     row = saved;
841           }
842 
843 
844             /*
845              * Work out where the line lies in the field in relation to
846              * the cursor_ypos.  First count the rows from the start of
847              * the field until we hit the row we just worked on.
848              */
849           saved = field->start_line;
850           count = 0;
851           while (saved->next != NULL) {
852                     if (saved == row)
853                               break;
854                     count++;
855                     saved = saved->next;
856           }
857 
858             /* now check if we need to adjust cursor_ypos */
859           if (field->cursor_ypos > count) {
860                     field->cursor_ypos--;
861           }
862 
863           field->row_count--;
864           *rowp = row;
865 
866             /* wrap the field if required, if this fails undo the change */
867           if ((direction == JOIN_NEXT) || (direction == JOIN_PREV)) {
868                     if (_formi_wrap_field(field, row) != E_OK) {
869                               return E_REQUEST_DENIED;
870                     }
871           }
872 
873           return E_OK;
874 }
875 
876 /*
877  * Split the line at the given position, if possible.  If hard_split is
878  * TRUE then split the line regardless of the position, otherwise don't
879  * split at the beginning of a line.
880  */
881 static int
split_line(FIELD * field,bool hard_split,unsigned pos,_FORMI_FIELD_LINES ** rowp)882 split_line(FIELD *field, bool hard_split, unsigned pos,
883              _FORMI_FIELD_LINES **rowp)
884 {
885           struct _formi_field_lines *new_line;
886           char *newp;
887           _FORMI_FIELD_LINES *row = *rowp;
888 
889             /* if asked to split right where the line already starts then
890              * just return - nothing to do unless we are appending a line
891              * to the buffer.
892              */
893           if ((pos == 0) && (hard_split == FALSE))
894                     return E_OK;
895 
896           _formi_dbg_printf("%s: splitting line at %d\n", __func__, pos);
897 
898             /* Need an extra line struct, check free list first */
899           if (field->free != NULL) {
900                     new_line = field->free;
901                     field->free = new_line->next;
902                     if (field->free != NULL)
903                               field->free->prev = NULL;
904           } else {
905                     if ((new_line = malloc(sizeof(*new_line))) == NULL)
906                               return E_SYSTEM_ERROR;
907                     new_line->prev = NULL;
908                     new_line->next = NULL;
909                     new_line->allocated = 0;
910                     new_line->length = 0;
911                     new_line->expanded = 0;
912                     new_line->string = NULL;
913                     new_line->hard_ret = FALSE;
914                     new_line->tabs = NULL;
915           }
916 
917           _formi_dbg_printf("%s: enter: length = %d, expanded = %d\n", __func__,
918               row->length, row->expanded);
919 
920           assert((row->length < INT_MAX) && (row->expanded < INT_MAX));
921 
922 
923             /* add new line to the row list */
924           new_line->next = row->next;
925           new_line->prev = row;
926           row->next = new_line;
927           if (new_line->next != NULL)
928                     new_line->next->prev = new_line;
929 
930           new_line->length = row->length - pos;
931           if (new_line->length >= new_line->allocated) {
932                     if ((newp = realloc(new_line->string,
933                                             (size_t) new_line->length + 1)) == NULL)
934                               return E_SYSTEM_ERROR;
935                     new_line->string = newp;
936                     new_line->allocated = new_line->length + 1;
937           }
938 
939           strcpy(new_line->string, &row->string[pos]);
940 
941           row->length = pos;
942           row->string[pos] = '\0';
943 
944           if (row->length != 0)
945                     row->expanded = _formi_tab_expanded_length(row->string, 0,
946                                                                          row->length - 1);
947           else
948                     row->expanded = 0;
949           _formi_calculate_tabs(row);
950 
951           if (new_line->length != 0)
952                     new_line->expanded =
953                               _formi_tab_expanded_length(new_line->string, 0,
954                                                                new_line->length - 1);
955           else
956                     new_line->expanded = 0;
957 
958           _formi_calculate_tabs(new_line);
959 
960             /*
961              * If the given row was the current line then adjust the
962              * current line pointer if necessary
963              */
964           if ((field->cur_line == row) && (field->row_xpos >= pos)) {
965                     field->cur_line = new_line;
966                     field->row_xpos -= pos;
967                     field->cursor_xpos =
968                               _formi_tab_expanded_length(new_line->string, 0,
969                                                                field->row_xpos);
970                     if (field->cursor_xpos > 0)
971                               field->cursor_xpos--;
972 
973                     field->cursor_ypos++;
974                     if (field->cursor_ypos >= field->rows) {
975                               if (field->start_line->next != NULL) {
976                                         field->start_line = field->start_line->next;
977                                         field->cursor_ypos = field->rows - 1;
978                               }
979                               else
980                                         assert(field->start_line->next == NULL);
981                     }
982           }
983 
984             /*
985              * If the line split had a hard return then replace the
986              * current line's hard return with a soft return and carry
987              * the hard return onto the line after.
988              */
989           if (row->hard_ret == TRUE) {
990                     new_line->hard_ret = TRUE;
991                     row->hard_ret = FALSE;
992           }
993 
994             /*
995              * except where we are doing a hard split then the current
996              * row must have a hard return on it too...
997              */
998           if (hard_split == TRUE) {
999                     row->hard_ret = TRUE;
1000           }
1001 
1002           assert(((row->expanded < INT_MAX) &&
1003                     (new_line->expanded < INT_MAX) &&
1004                     (row->length < INT_MAX) &&
1005                     (new_line->length < INT_MAX)));
1006 
1007           _formi_dbg_printf("%s: exit: ", __func__);
1008           _formi_dbg_printf("row.length = %d, row.expanded = %d, ",
1009               row->length, row->expanded);
1010           _formi_dbg_printf("next_line.length = %d, next_line.expanded = %d, ",
1011               new_line->length, new_line->expanded);
1012           _formi_dbg_printf("row_count = %d\n", field->row_count + 1);
1013 
1014           field->row_count++;
1015           *rowp = new_line;
1016 
1017           return E_OK;
1018 }
1019 
1020 /*
1021  * skip the blanks in the given string, start at the index start and
1022  * continue forward until either the end of the string or a non-blank
1023  * character is found.  Return the index of either the end of the string or
1024  * the first non-blank character.
1025  */
1026 unsigned
_formi_skip_blanks(char * string,unsigned int start)1027 _formi_skip_blanks(char *string, unsigned int start)
1028 {
1029           unsigned int i;
1030 
1031           i = start;
1032 
1033           while ((string[i] != '\0') && isblank((unsigned char)string[i]))
1034                     i++;
1035 
1036           return i;
1037 }
1038 
1039 /*
1040  * Skip the blanks in the string associated with the given row, pass back
1041  * the row and the offset at which the first non-blank is found.  If no
1042  * non-blank character is found then return the index to the last
1043  * character on the last line.
1044  */
1045 
1046 unsigned
field_skip_blanks(unsigned int start,_FORMI_FIELD_LINES ** rowp)1047 field_skip_blanks(unsigned int start, _FORMI_FIELD_LINES **rowp)
1048 {
1049           unsigned int i;
1050           _FORMI_FIELD_LINES *row, *last = NULL;
1051 
1052           row = *rowp;
1053           i = start;
1054 
1055           do {
1056                     i = _formi_skip_blanks(&row->string[i], i);
1057                     if (!isblank((unsigned char)row->string[i])) {
1058                               last = row;
1059                               row = row->next;
1060                                 /*
1061                                  * don't reset if last line otherwise we will
1062                                  * not be at the end of the string.
1063                                  */
1064                               if (row != NULL)
1065                                         i = 0;
1066                     } else
1067                               break;
1068           }
1069           while (row != NULL);
1070 
1071             /*
1072              * If we hit the end of the row list then point at the last row
1073              * otherwise we return the row we found the blank on.
1074              */
1075           if (row == NULL)
1076                     *rowp = last;
1077           else
1078                     *rowp = row;
1079 
1080           return i;
1081 }
1082 
1083 /*
1084  * Return the index of the top left most field of the two given fields.
1085  */
1086 static int
_formi_top_left(FORM * form,int a,int b)1087 _formi_top_left(FORM *form, int a, int b)
1088 {
1089             /* lower row numbers always win here.... */
1090           if (form->fields[a]->form_row < form->fields[b]->form_row)
1091                     return a;
1092 
1093           if (form->fields[a]->form_row > form->fields[b]->form_row)
1094                     return b;
1095 
1096             /* rows must be equal, check columns */
1097           if (form->fields[a]->form_col < form->fields[b]->form_col)
1098                     return a;
1099 
1100           if (form->fields[a]->form_col > form->fields[b]->form_col)
1101                     return b;
1102 
1103             /* if we get here fields must be in exactly the same place, punt */
1104           return a;
1105 }
1106 
1107 /*
1108  * Return the index to the field that is the bottom-right-most of the
1109  * two given fields.
1110  */
1111 static int
_formi_bottom_right(FORM * form,int a,int b)1112 _formi_bottom_right(FORM *form, int a, int b)
1113 {
1114             /* check the rows first, biggest row wins */
1115           if (form->fields[a]->form_row > form->fields[b]->form_row)
1116                     return a;
1117           if (form->fields[a]->form_row < form->fields[b]->form_row)
1118                     return b;
1119 
1120             /* rows must be equal, check cols, biggest wins */
1121           if (form->fields[a]->form_col > form->fields[b]->form_col)
1122                     return a;
1123           if (form->fields[a]->form_col < form->fields[b]->form_col)
1124                     return b;
1125 
1126             /* fields in the same place, punt */
1127           return a;
1128 }
1129 
1130 /*
1131  * Find the end of the current word in the string str, starting at
1132  * offset - the end includes any trailing whitespace.  If the end of
1133  * the string is found before a new word then just return the offset
1134  * to the end of the string.  If do_join is TRUE then lines will be
1135  * joined (without wrapping) until either the end of the field or the
1136  * end of a word is found (whichever comes first).
1137  */
1138 static int
find_eow(FIELD * cur,unsigned int offset,bool do_join,_FORMI_FIELD_LINES ** rowp)1139 find_eow(FIELD *cur, unsigned int offset, bool do_join,
1140            _FORMI_FIELD_LINES **rowp)
1141 {
1142           int start;
1143           _FORMI_FIELD_LINES *row;
1144 
1145           row = *rowp;
1146           start = offset;
1147 
1148           do {
1149                       /* first skip any non-whitespace */
1150                     while ((row->string[start] != '\0')
1151                            && !isblank((unsigned char)row->string[start]))
1152                               start++;
1153 
1154                       /* see if we hit the end of the string */
1155                     if (row->string[start] == '\0') {
1156                               if (do_join == TRUE) {
1157                                         if (row->next == NULL)
1158                                                   return start;
1159 
1160                                         if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1161                                             != E_OK)
1162                                                   return E_REQUEST_DENIED;
1163                               } else {
1164                                         do {
1165                                                   if (row->next == NULL) {
1166                                                             *rowp = row;
1167                                                             return start;
1168                                                   } else {
1169                                                             row = row->next;
1170                                                             start = 0;
1171                                                   }
1172                                         } while (row->length == 0);
1173                               }
1174                     }
1175           } while (!isblank((unsigned char)row->string[start]));
1176 
1177           do {
1178                       /* otherwise skip the whitespace.... */
1179                     while ((row->string[start] != '\0')
1180                            && isblank((unsigned char)row->string[start]))
1181                               start++;
1182 
1183                     if (row->string[start] == '\0') {
1184                               if (do_join == TRUE) {
1185                                         if (row->next == NULL)
1186                                                   return start;
1187 
1188                                         if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1189                                             != E_OK)
1190                                                   return E_REQUEST_DENIED;
1191                               } else {
1192                                         do {
1193                                                   if (row->next == NULL) {
1194                                                             *rowp = row;
1195                                                             return start;
1196                                                   } else {
1197                                                             row = row->next;
1198                                                             start = 0;
1199                                                   }
1200                                         } while (row->length == 0);
1201                               }
1202                     }
1203           } while (isblank((unsigned char)row->string[start]));
1204 
1205           *rowp = row;
1206           return start;
1207 }
1208 
1209 /*
1210  * Find the beginning of the current word in the string str, starting
1211  * at offset.
1212  */
1213 static int
find_sow(unsigned int offset,_FORMI_FIELD_LINES ** rowp)1214 find_sow(unsigned int offset, _FORMI_FIELD_LINES **rowp)
1215 {
1216           int start;
1217           char *str;
1218           _FORMI_FIELD_LINES *row;
1219 
1220           row = *rowp;
1221           str = row->string;
1222           start = offset;
1223 
1224           do {
1225                     if (start > 0) {
1226                               if (isblank((unsigned char)str[start]) ||
1227                                   isblank((unsigned char)str[start - 1])) {
1228                                         if (isblank((unsigned char)str[start - 1]))
1229                                                   start--;
1230                                           /* skip the whitespace.... */
1231                                         while ((start >= 0) &&
1232                                             isblank((unsigned char)str[start]))
1233                                                   start--;
1234                               }
1235                     }
1236 
1237                       /* see if we hit the start of the string */
1238                     if (start < 0) {
1239                               do {
1240                                         if (row->prev == NULL) {
1241                                                   *rowp = row;
1242                                                   start = 0;
1243                                                   return start;
1244                                         } else {
1245                                                   row = row->prev;
1246                                                   str = row->string;
1247                                                   if (row->length > 0)
1248                                                             start = row->length - 1;
1249                                                   else
1250                                                             start = 0;
1251                                         }
1252                               } while (row->length == 0);
1253                     }
1254           } while (isblank((unsigned char)row->string[start]));
1255 
1256             /* see if we hit the start of the string */
1257           if (start < 0) {
1258                     *rowp = row;
1259                     return 0;
1260           }
1261 
1262             /* now skip any non-whitespace */
1263           do {
1264                     while ((start >= 0) && !isblank((unsigned char)str[start]))
1265                               start--;
1266 
1267 
1268                     if (start < 0) {
1269                               do {
1270                                         if (row->prev == NULL) {
1271                                                   *rowp = row;
1272                                                   start = 0;
1273                                                   return start;
1274                                         } else {
1275                                                   row = row->prev;
1276                                                   str = row->string;
1277                                                   if (row->length > 0)
1278                                                             start = row->length - 1;
1279                                                   else
1280                                                             start = 0;
1281                                         }
1282                               } while (row->length == 0);
1283                     }
1284           } while (!isblank((unsigned char)str[start]));
1285 
1286           if (start > 0) {
1287                     start++; /* last loop has us pointing at a space, adjust */
1288                     if (start >= row->length) {
1289                               if (row->next != NULL) {
1290                                         start = 0;
1291                                         row = row->next;
1292                               } else {
1293                                         start = row->length - 1;
1294                               }
1295                     }
1296           }
1297 
1298           if (start < 0)
1299                     start = 0;
1300 
1301           *rowp = row;
1302           return start;
1303 }
1304 
1305 /*
1306  * Scroll the field forward the given number of lines.
1307  */
1308 static void
_formi_scroll_fwd(FIELD * field,unsigned int amt)1309 _formi_scroll_fwd(FIELD *field, unsigned int amt)
1310 {
1311           unsigned int count;
1312           _FORMI_FIELD_LINES *end_row;
1313 
1314           end_row = field->start_line;
1315             /* walk the line structs forward to find the bottom of the field */
1316           count = field->rows - 1;
1317           while ((count > 0) && (end_row->next != NULL))
1318           {
1319                     count--;
1320                     end_row = end_row->next;
1321           }
1322 
1323             /* check if there are lines to scroll */
1324           if ((count > 0) && (end_row->next == NULL))
1325                     return;
1326 
1327             /*
1328              * ok, lines to scroll - do this by walking both the start_line
1329              * and the end_row at the same time for amt lines, we stop when
1330              * either we have done the number of lines or end_row hits the
1331              * last line in the field.
1332              */
1333           count = amt;
1334           while ((count > 0) && (end_row->next != NULL)) {
1335                     count--;
1336                     field->start_line = field->start_line->next;
1337                     end_row = end_row->next;
1338           }
1339 }
1340 
1341 /*
1342  * Scroll the field backward the given number of lines.
1343  */
1344 static void
_formi_scroll_back(FIELD * field,unsigned int amt)1345 _formi_scroll_back(FIELD *field, unsigned int amt)
1346 {
1347           unsigned int count;
1348 
1349             /* check for lines above */
1350           if (field->start_line->prev == NULL)
1351                     return;
1352 
1353             /*
1354              * Backward scroll is easy, follow row struct chain backward until
1355              * the number of lines done or we reach the top of the field.
1356              */
1357           count = amt;
1358           while ((count > 0) && (field->start_line->prev != NULL)) {
1359                     count--;
1360                     field->start_line = field->start_line->prev;
1361           }
1362 }
1363 
1364 /*
1365  * Scroll the field forward the given number of characters.
1366  */
1367 void
_formi_hscroll_fwd(FIELD * field,_FORMI_FIELD_LINES * row,int unsigned amt)1368 _formi_hscroll_fwd(FIELD *field, _FORMI_FIELD_LINES *row, int unsigned amt)
1369 {
1370           unsigned int end, scroll_amt, expanded;
1371           _formi_tab_t *ts;
1372 
1373 
1374           if ((row->tabs == NULL) || (row->tabs->in_use == FALSE)) {
1375                       /* if the line has no tabs things are easy... */
1376                     end =  field->start_char + field->cols + amt - 1;
1377                     scroll_amt = amt;
1378                     if (end > row->length) {
1379                               end = row->length;
1380                               scroll_amt = end - field->start_char - field->cols + 1;
1381                     }
1382           } else {
1383                       /*
1384                        * If there are tabs we need to add on the scroll amount,
1385                        * find the last char position that will fit into
1386                        * the field and finally fix up the start_char.  This
1387                        * is a lot of work but handling the case where there
1388                        * are not enough chars to scroll by amt is difficult.
1389                        */
1390                     end = field->start_char + field->row_xpos + amt;
1391                     if (end >= row->length)
1392                               end = row->length - 1;
1393                     else {
1394                               expanded = _formi_tab_expanded_length(
1395                                         row->string,
1396                                         field->start_char + amt,
1397                                         field->start_char + field->row_xpos + amt);
1398                               ts = row->tabs;
1399                                 /* skip tabs to the lhs of our starting point */
1400                               while ((ts != NULL) && (ts->in_use == TRUE)
1401                                      && (ts->pos < end))
1402                                         ts = ts->fwd;
1403 
1404                               while ((expanded <= field->cols)
1405                                      && (end < row->length)) {
1406                                         if (row->string[end] == '\t') {
1407                                                   assert((ts != NULL)
1408                                                          && (ts->in_use == TRUE));
1409                                                   if (ts->pos == end) {
1410                                                             if ((expanded + ts->size)
1411                                                                 > field->cols)
1412                                                                       break;
1413                                                             expanded += ts->size;
1414                                                             ts = ts->fwd;
1415                                                   }
1416                                                   else
1417                                                             assert(ts->pos == end);
1418                                         } else
1419                                                   expanded++;
1420                                         end++;
1421                               }
1422                     }
1423 
1424                     scroll_amt = tab_fit_window(field, end, field->cols);
1425                     if (scroll_amt < field->start_char)
1426                               scroll_amt = 1;
1427                     else
1428                               scroll_amt -= field->start_char;
1429 
1430                     scroll_amt = min(scroll_amt, amt);
1431           }
1432 
1433           field->start_char += scroll_amt;
1434           field->cursor_xpos =
1435                     _formi_tab_expanded_length(row->string,
1436                                                      field->start_char,
1437                                                      field->row_xpos
1438                                                      + field->start_char) - 1;
1439 
1440 }
1441 
1442 /*
1443  * Scroll the field backward the given number of characters.
1444  */
1445 void
_formi_hscroll_back(FIELD * field,_FORMI_FIELD_LINES * row,unsigned int amt)1446 _formi_hscroll_back(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt)
1447 {
1448           field->start_char -= min(field->start_char, amt);
1449           field->cursor_xpos =
1450                     _formi_tab_expanded_length(row->string, field->start_char,
1451                                                      field->row_xpos
1452                                                      + field->start_char) - 1;
1453           if (field->cursor_xpos >= field->cols) {
1454                     field->row_xpos = 0;
1455                     field->cursor_xpos = 0;
1456           }
1457 }
1458 
1459 /*
1460  * Find the different pages in the form fields and assign the form
1461  * page_starts array with the information to find them.
1462  */
1463 int
_formi_find_pages(FORM * form)1464 _formi_find_pages(FORM *form)
1465 {
1466           int i, cur_page = 0;
1467 
1468           if ((form->page_starts = calloc((form->max_page + 1),
1469               sizeof(*form->page_starts))) == NULL)
1470                     return E_SYSTEM_ERROR;
1471 
1472           for (i = 0; i < form->field_count; i++) {
1473                     if (form->fields[i]->page_break == 1)
1474                               cur_page++;
1475                     if (form->page_starts[cur_page].in_use == 0) {
1476                               form->page_starts[cur_page].in_use = 1;
1477                               form->page_starts[cur_page].first = i;
1478                               form->page_starts[cur_page].last = i;
1479                               form->page_starts[cur_page].top_left = i;
1480                               form->page_starts[cur_page].bottom_right = i;
1481                     } else {
1482                               form->page_starts[cur_page].last = i;
1483                               form->page_starts[cur_page].top_left =
1484                                         _formi_top_left(form,
1485                                                             form->page_starts[cur_page].top_left,
1486                                                             i);
1487                               form->page_starts[cur_page].bottom_right =
1488                                         _formi_bottom_right(form,
1489                                                                 form->page_starts[cur_page].bottom_right,
1490                                                                 i);
1491                     }
1492           }
1493 
1494           return E_OK;
1495 }
1496 
1497 /*
1498  * Completely redraw the field of the given form.
1499  */
1500 void
_formi_redraw_field(FORM * form,int field)1501 _formi_redraw_field(FORM *form, int field)
1502 {
1503           unsigned int pre, post, flen, slen, i, j, start, line;
1504           unsigned int tab, cpos, len;
1505           char *str, c;
1506           FIELD *cur;
1507           _FORMI_FIELD_LINES *row;
1508 #ifdef DEBUG
1509           char buffer[100];
1510 #endif
1511 
1512           cur = form->fields[field];
1513           flen = cur->cols;
1514           slen = 0;
1515           start = 0;
1516           line = 0;
1517 
1518           for (row = cur->start_line; ((row != NULL) && (line < cur->rows));
1519                row = row->next, line++) {
1520                     wmove(form->scrwin, (int) (cur->form_row + line),
1521                           (int) cur->form_col);
1522                     if ((cur->rows + cur->nrows) == 1) {
1523                               if ((cur->cols + cur->start_char) >= row->length)
1524                                         len = row->length;
1525                               else
1526                                         len = cur->cols + cur->start_char;
1527                               if (row->string != NULL)
1528                                         slen = _formi_tab_expanded_length(
1529                                                   row->string, cur->start_char, len);
1530                               else
1531                                         slen = 0;
1532 
1533                               if (slen > cur->cols)
1534                                         slen = cur->cols;
1535                               slen += cur->start_char;
1536                     } else
1537                               slen = row->expanded;
1538 
1539                     if ((cur->opts & O_STATIC) == O_STATIC) {
1540                               switch (cur->justification) {
1541                               case JUSTIFY_RIGHT:
1542                                         post = 0;
1543                                         if (flen < slen)
1544                                                   pre = 0;
1545                                         else
1546                                                   pre = flen - slen;
1547                                         break;
1548 
1549                               case JUSTIFY_CENTER:
1550                                         if (flen < slen) {
1551                                                   pre = 0;
1552                                                   post = 0;
1553                                         } else {
1554                                                   pre = flen - slen;
1555                                                   post = pre = pre / 2;
1556                                                     /* get padding right if
1557                                                        centring is not even */
1558                                                   if ((post + pre + slen) < flen)
1559                                                             post++;
1560                                         }
1561                                         break;
1562 
1563                               case NO_JUSTIFICATION:
1564                               case JUSTIFY_LEFT:
1565                               default:
1566                                         pre = 0;
1567                                         if (flen <= slen)
1568                                                   post = 0;
1569                                         else {
1570                                                   post = flen - slen;
1571                                                   if (post > flen)
1572                                                             post = flen;
1573                                         }
1574                                         break;
1575                               }
1576                     } else {
1577                                 /* dynamic fields are not justified */
1578                               pre = 0;
1579                               if (flen <= slen)
1580                                         post = 0;
1581                               else {
1582                                         post = flen - slen;
1583                                         if (post > flen)
1584                                                   post = flen;
1585                               }
1586 
1587                                 /* but they do scroll.... */
1588 
1589                               if (pre > cur->start_char - start)
1590                                         pre = pre - cur->start_char + start;
1591                               else
1592                                         pre = 0;
1593 
1594                               if (slen > cur->start_char) {
1595                                         slen -= cur->start_char;
1596                                         if (slen > flen)
1597                                                   post = 0;
1598                                         else
1599                                                   post = flen - slen;
1600 
1601                                         if (post > flen)
1602                                                   post = flen;
1603                               } else {
1604                                         slen = 0;
1605                                         post = flen - pre;
1606                               }
1607                     }
1608 
1609                     str = &row->string[cur->start_char];
1610 
1611 #ifdef DEBUG
1612                     _formi_dbg_printf(
1613                         "%s: start=%d, pre=%d, slen=%d, flen=%d, post=%d, "
1614                         "start_char=%d\n", __func__,
1615                         start, pre, slen, flen, post, cur->start_char);
1616                     if (str != NULL) {
1617                               if (row->expanded != 0) {
1618                                         strncpy(buffer, str, flen);
1619                               } else {
1620                                         strcpy(buffer, "(empty)");
1621                               }
1622                     } else {
1623                               strcpy(buffer, "(null)");
1624                     }
1625                     buffer[flen] = '\0';
1626                     _formi_dbg_printf("%s: %s\n", __func__,  buffer);
1627 #endif
1628 
1629                     wattrset(form->scrwin, cur->back);
1630 
1631                     for (i = start + cur->start_char; i < pre; i++)
1632                               waddch(form->scrwin, cur->pad);
1633 
1634                     _formi_dbg_printf("%s: will add %d chars\n", __func__,
1635                               min(slen, flen));
1636                     wattrset(form->scrwin, cur->fore);
1637                     for (i = 0, cpos = cur->start_char; i < min(slen, flen);
1638                          i++, str++, cpos++)
1639                     {
1640                               c = *str;
1641                               tab = 0; /* just to shut gcc up */
1642                               _formi_dbg_printf("adding char str[%d]=%c\n",
1643                                   cpos + cur->start_char,   c);
1644                               if (((cur->opts & O_PUBLIC) != O_PUBLIC)) {
1645                                         if (c == '\t')
1646                                                   tab = add_tab(form, row, cpos,
1647                                                                   cur->pad);
1648                                         else {
1649                                                   wattrset(form->scrwin, cur->back);
1650                                                   waddch(form->scrwin, cur->pad);
1651                                                   wattrset(form->scrwin, cur->fore);
1652                                         }
1653                               } else if ((cur->opts & O_VISIBLE) == O_VISIBLE) {
1654                                         if (c == '\t')
1655                                                   tab = add_tab(form, row, cpos, ' ');
1656                                         else
1657                                                   waddch(form->scrwin, c);
1658                               } else {
1659                                         if (c == '\t')
1660                                                   tab = add_tab(form, row, cpos, ' ');
1661                                         else
1662                                                   waddch(form->scrwin, ' ');
1663                               }
1664 
1665                                 /*
1666                                  * If we have had a tab then skip forward
1667                                  * the requisite number of chars to keep
1668                                  * things in sync.
1669                                  */
1670                               if (c == '\t')
1671                                         i += tab - 1;
1672                     }
1673 
1674                     wattrset(form->scrwin, cur->back);
1675                     for (i = 0; i < post; i++)
1676                               waddch(form->scrwin, cur->pad);
1677           }
1678 
1679           for (i = line; i < cur->rows; i++) {
1680                     wmove(form->scrwin, (int) (cur->form_row + i),
1681                           (int) cur->form_col);
1682 
1683                     wattrset(form->scrwin, cur->back);
1684 
1685                     for (j = 0; j < cur->cols; j++) {
1686                               waddch(form->scrwin, cur->pad);
1687                     }
1688           }
1689 
1690           wattrset(form->scrwin, cur->back);
1691 
1692           cur = form->fields[form->cur_field];
1693           wmove(form->scrwin, cur->form_row + cur->cursor_ypos,
1694               cur->form_col + cur->cursor_xpos);
1695           wcursyncup(form->scrwin);
1696 
1697           return;
1698 }
1699 
1700 /*
1701  * Add the correct number of the given character to simulate a tab
1702  * in the field.
1703  */
1704 static int
add_tab(FORM * form,_FORMI_FIELD_LINES * row,unsigned int i,char c)1705 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c)
1706 {
1707           int j;
1708           _formi_tab_t *ts = row->tabs;
1709 
1710           while ((ts != NULL) && (ts->pos != i))
1711                     ts = ts->fwd;
1712 
1713           assert(ts != NULL);
1714 
1715           for (j = 0; j < ts->size; j++)
1716                     waddch(form->scrwin, c);
1717 
1718           return ts->size;
1719 }
1720 
1721 
1722 /*
1723  * Display the fields attached to the form that are on the current page
1724  * on the screen.
1725  *
1726  */
1727 int
_formi_draw_page(FORM * form)1728 _formi_draw_page(FORM *form)
1729 {
1730           int i;
1731 
1732           if (form->page_starts[form->page].in_use == 0)
1733                     return E_BAD_ARGUMENT;
1734 
1735           wclear(form->scrwin);
1736 
1737           for (i = form->page_starts[form->page].first;
1738                i <= form->page_starts[form->page].last; i++)
1739                     _formi_redraw_field(form, i);
1740 
1741           return E_OK;
1742 }
1743 
1744 /*
1745  * Add the character c at the position pos in buffer 0 of the given field
1746  */
1747 int
_formi_add_char(FIELD * field,unsigned int pos,char c)1748 _formi_add_char(FIELD *field, unsigned int pos, char c)
1749 {
1750           char *new, old_c;
1751           unsigned int new_size;
1752           int status;
1753           _FORMI_FIELD_LINES *row, *temp, *next_temp;
1754 
1755           row = field->cur_line;
1756 
1757             /*
1758              * If buffer has not had a string before, set it to a blank
1759              * string.  Everything should flow from there....
1760              */
1761           if (row->string == NULL) {
1762                     if ((row->string = malloc((size_t)INITIAL_LINE_ALLOC)) == NULL)
1763                               return E_SYSTEM_ERROR;
1764                     row->string[0] = '\0';
1765                     row->allocated = INITIAL_LINE_ALLOC;
1766                     row->length = 0;
1767                     row->expanded = 0;
1768           }
1769 
1770           if (_formi_validate_char(field, c) != E_OK) {
1771                     _formi_dbg_printf("%s: char %c failed char validation\n",
1772                         __func__, c);
1773                     return E_INVALID_FIELD;
1774           }
1775 
1776           if ((c == '\t') && (field->cols <= 8)) {
1777                     _formi_dbg_printf("%s: field too small for a tab\n", __func__);
1778                     return E_NO_ROOM;
1779           }
1780 
1781           _formi_dbg_printf("%s: pos=%d, char=%c\n", __func__, pos, c);
1782           _formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__,
1783                     field->cursor_xpos, field->row_xpos, field->start_char);
1784           _formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__,
1785                     row->expanded, row->length, row->allocated);
1786           _formi_dbg_printf("%s: %s\n", __func__, row->string);
1787           _formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status);
1788           if (((field->opts & O_BLANK) == O_BLANK) &&
1789               (field->buf0_status == FALSE) &&
1790               ((field->row_xpos + field->start_char) == 0)) {
1791                     row = field->alines;
1792                     if (row->next != NULL) {
1793                                 /* shift all but one line structs to free list */
1794                               temp = row->next;
1795                               do {
1796                                         next_temp = temp->next;
1797                                         add_to_free(field, temp);
1798                                         temp = next_temp;
1799                               } while (temp != NULL);
1800                     }
1801 
1802                     row->length = 0;
1803                     row->string[0] = '\0';
1804                     pos = 0;
1805                     field->start_char = 0;
1806                     field->start_line = row;
1807                     field->cur_line = row;
1808                     field->row_count = 1;
1809                     field->row_xpos = 0;
1810                     field->cursor_ypos = 0;
1811                     row->expanded = 0;
1812                     row->length = 0;
1813                     _formi_init_field_xpos(field);
1814           }
1815 
1816 
1817           if ((field->overlay == 0)
1818               || ((field->overlay == 1) && (pos >= row->length))) {
1819                       /* first check if the field can have more chars...*/
1820                     if (check_field_size(field) == FALSE)
1821                               return E_REQUEST_DENIED;
1822 
1823                     if (row->length + 2
1824                         >= row->allocated) {
1825                               new_size = row->allocated + 16 - (row->allocated % 16);
1826                               if ((new = realloc(row->string,
1827                                                               (size_t) new_size )) == NULL)
1828                                         return E_SYSTEM_ERROR;
1829                               row->allocated = new_size;
1830                               row->string = new;
1831                     }
1832           }
1833 
1834           if ((field->overlay == 0) && (row->length > pos)) {
1835                     memmove(&row->string[pos + 1], &row->string[pos],
1836                           (size_t) (row->length - pos + 1));
1837           }
1838 
1839           old_c = row->string[pos];
1840           row->string[pos] = c;
1841           if (pos >= row->length) {
1842                       /* make sure the string is terminated if we are at the
1843                        * end of the string, the terminator would be missing
1844                        * if we are at the end of the field.
1845                        */
1846                     row->string[pos + 1] = '\0';
1847           }
1848 
1849             /* only increment the length if we are inserting characters
1850              * OR if we are at the end of the field in overlay mode.
1851              */
1852           if ((field->overlay == 0)
1853               || ((field->overlay == 1) && (pos >= row->length))) {
1854                     row->length++;
1855           }
1856 
1857           _formi_calculate_tabs(row);
1858           row->expanded = _formi_tab_expanded_length(row->string, 0,
1859                                                                row->length - 1);
1860 
1861             /* wrap the field, if needed */
1862           status = _formi_wrap_field(field, row);
1863 
1864           row = field->cur_line;
1865           pos = field->row_xpos;
1866 
1867             /*
1868              * check the wrap worked or that we have not exceeded the
1869              * max field size - this can happen if the field is re-wrapped
1870              * and the row count is increased past the set limit.
1871              */
1872           if ((status != E_OK) || (check_field_size(field) == FALSE)) {
1873                     if ((field->overlay == 0)
1874                         || ((field->overlay == 1)
1875                               && (pos >= (row->length - 1) /*XXXX- append check???*/))) {
1876                                 /*
1877                                  * wrap failed for some reason, back out the
1878                                  * char insert
1879                                  */
1880                               memmove(&row->string[pos], &row->string[pos + 1],
1881                                     (size_t) (row->length - pos));
1882                               row->length--;
1883                               if (pos > 0)
1884                                         pos--;
1885                     } else if (field->overlay == 1) {
1886                                 /* back out character overlay */
1887                               row->string[pos] = old_c;
1888                     }
1889 
1890                     _formi_calculate_tabs(row);
1891 
1892                     _formi_wrap_field(field, row);
1893                       /*
1894                        * If we are here then either the status is bad or we
1895                        * simply ran out of room.  If the status is E_OK then
1896                        * we ran out of room, let the form driver know this.
1897                        */
1898                     if (status == E_OK)
1899                               status = E_REQUEST_DENIED;
1900 
1901           } else {
1902                     field->buf0_status = TRUE;
1903                     field->row_xpos++;
1904                     if ((field->rows + field->nrows) == 1) {
1905                               status = _formi_set_cursor_xpos(field, FALSE);
1906                     } else {
1907                               field->cursor_xpos =
1908                                         _formi_tab_expanded_length(
1909                                                   row->string, 0,     field->row_xpos - 1);
1910 
1911                                 /*
1912                                  * Annoying corner case - if we are right in
1913                                  * the bottom right corner of the field we
1914                                  * need to scroll the field one line so the
1915                                  * cursor is positioned correctly in the
1916                                  * field.
1917                                  */
1918                               if ((field->cursor_xpos >= field->cols) &&
1919                                   (field->cursor_ypos == (field->rows - 1))) {
1920                                         field->cursor_ypos--;
1921                                         field->start_line = field->start_line->next;
1922                               }
1923                     }
1924           }
1925 
1926           assert((field->cursor_xpos <= field->cols)
1927                  && (field->cursor_ypos < 400000));
1928 
1929           _formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__,
1930                     field->cursor_xpos, field->row_xpos, field->start_char);
1931           _formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__,
1932                     row->expanded, row->length, row->allocated);
1933           _formi_dbg_printf("%s: ypos=%d, start_line=%p\n", __func__,
1934                     field->cursor_ypos, field->start_line);
1935           _formi_dbg_printf("%s: %s\n", __func__, row->string);
1936           _formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status);
1937           _formi_dbg_printf("%s: status = %s\n", __func__,
1938                     (status == E_OK)? "OK" : "FAILED");
1939           return status;
1940 }
1941 
1942 /*
1943  * Set the position of the cursor on the screen in the row depending on
1944  * where the current position in the string is and the justification
1945  * that is to be applied to the field.  Justification is only applied
1946  * to single row, static fields.
1947  */
1948 static int
_formi_set_cursor_xpos(FIELD * field,int noscroll)1949 _formi_set_cursor_xpos(FIELD *field, int noscroll)
1950 {
1951           int just, pos;
1952 
1953           just = field->justification;
1954           pos = field->start_char + field->row_xpos;
1955 
1956           _formi_dbg_printf(
1957               "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__,
1958               pos, field->start_char, field->row_xpos, field->cursor_xpos);
1959 
1960             /*
1961              * make sure we apply the correct justification to non-static
1962              * fields.
1963              */
1964           if (((field->rows + field->nrows) != 1) ||
1965               ((field->opts & O_STATIC) != O_STATIC))
1966                     just = JUSTIFY_LEFT;
1967 
1968           switch (just) {
1969           case JUSTIFY_RIGHT:
1970                     field->cursor_xpos = field->cols - 1
1971                               - _formi_tab_expanded_length(
1972                                         field->cur_line->string, 0,
1973                                         field->cur_line->length - 1)
1974                               + _formi_tab_expanded_length(
1975                                         field->cur_line->string, 0,
1976                                         field->row_xpos);
1977                     break;
1978 
1979           case JUSTIFY_CENTER:
1980                     field->cursor_xpos = ((field->cols - 1)
1981                               - _formi_tab_expanded_length(
1982                                         field->cur_line->string, 0,
1983                                         field->cur_line->length - 1) + 1) / 2
1984                               + _formi_tab_expanded_length(field->cur_line->string,
1985                                                                  0, field->row_xpos);
1986 
1987                     if (field->cursor_xpos > (field->cols - 1))
1988                               field->cursor_xpos = (field->cols - 1);
1989                     break;
1990 
1991           default:
1992                     field->cursor_xpos = _formi_tab_expanded_length(
1993                               field->cur_line->string,
1994                               field->start_char,
1995                               field->row_xpos     + field->start_char);
1996                     if ((field->cursor_xpos <= (field->cols - 1)) &&
1997                         ((field->start_char + field->row_xpos)
1998                          < field->cur_line->length))
1999                               field->cursor_xpos--;
2000 
2001                     if (field->cursor_xpos > (field->cols - 1)) {
2002                               if ((field->opts & O_STATIC) == O_STATIC) {
2003                                         field->start_char = 0;
2004 
2005                                         if (field->row_xpos
2006                                             == (field->cur_line->length - 1)) {
2007                                                   field->cursor_xpos = field->cols - 1;
2008                                         } else {
2009                                                   field->cursor_xpos =
2010                                                             _formi_tab_expanded_length(
2011                                                               field->cur_line->string,
2012                                                               field->start_char,
2013                                                               field->row_xpos
2014                                                                 + field->start_char
2015                                                               - 1) - 1;
2016                                         }
2017                               } else {
2018                                         if (noscroll == FALSE) {
2019                                                   field->start_char =
2020                                                             tab_fit_window(
2021                                                                       field,
2022                                                                       field->start_char
2023                                                                       + field->row_xpos,
2024                                                                       field->cols);
2025                                                   field->row_xpos = pos
2026                                                             - field->start_char;
2027                                                   field->cursor_xpos =
2028                                                             _formi_tab_expanded_length(
2029                                                                field->cur_line->string,
2030                                                                field->start_char,
2031                                                                field->row_xpos
2032                                                                   + field->start_char - 1);
2033                                         } else {
2034                                                   field->cursor_xpos = (field->cols - 1);
2035                                         }
2036                               }
2037 
2038                     }
2039                     break;
2040           }
2041 
2042           _formi_dbg_printf(
2043               "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__,
2044               pos, field->start_char, field->row_xpos, field->cursor_xpos);
2045           return E_OK;
2046 }
2047 
2048 /*
2049  * Manipulate the text in a field, this takes the given form and performs
2050  * the passed driver command on the current text field.  Returns 1 if the
2051  * text field was modified.
2052  */
2053 int
_formi_manipulate_field(FORM * form,int c)2054 _formi_manipulate_field(FORM *form, int c)
2055 {
2056           FIELD *cur;
2057           char *str, saved;
2058           unsigned int start, end, pos, status, old_count, size;
2059           unsigned int old_xpos, old_row_pos;
2060           int len, wb;
2061           bool eat_char;
2062           _FORMI_FIELD_LINES *row, *rs;
2063 
2064           cur = form->fields[form->cur_field];
2065           if (cur->cur_line->string == NULL)
2066                     return E_REQUEST_DENIED;
2067 
2068           _formi_dbg_printf("%s: request is REQ_%s\n",
2069               __func__, reqs[c - REQ_MIN_REQUEST]);
2070           _formi_dbg_printf(
2071               "%s: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
2072               __func__, cur->cursor_xpos, cur->row_xpos, cur->start_char,
2073               cur->cur_line->length, cur->cur_line->allocated);
2074           _formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__,
2075               cur->start_line, cur->cursor_ypos);
2076           if (cur->cur_line->string == NULL)
2077                     _formi_dbg_printf("%s: string=(null)\n", __func__);
2078           else
2079                     _formi_dbg_printf("%s: string=\"%s\"\n", __func__,
2080                         cur->cur_line->string);
2081 
2082             /* Cannot manipulate a null string! */
2083           if (cur->cur_line->string == NULL)
2084                     return E_REQUEST_DENIED;
2085 
2086           saved = '\0';
2087           row = cur->cur_line;
2088 
2089           switch (c) {
2090           case REQ_RIGHT_CHAR:
2091                       /*
2092                        * The right_char request performs the same function
2093                        * as the next_char request except that the cursor is
2094                        * not wrapped if it is at the end of the line, so
2095                        * check if the cursor is at the end of the line and
2096                        * deny the request otherwise just fall through to
2097                        * the next_char request handler.
2098                        */
2099                     if (cur->cursor_xpos >= cur->cols - 1)
2100                               return E_REQUEST_DENIED;
2101 
2102                       /* FALLTHRU */
2103 
2104           case REQ_NEXT_CHAR:
2105                       /* for a dynamic field allow an offset of one more
2106                        * char so we can insert chars after end of string.
2107                        * Static fields cannot do this so deny request if
2108                        * cursor is at the end of the field.
2109                        */
2110                     if (((cur->opts & O_STATIC) == O_STATIC) &&
2111                         (cur->row_xpos == cur->cols - 1) &&
2112                         ((cur->rows + cur->nrows) == 1))
2113                               return E_REQUEST_DENIED;
2114 
2115                     if (((cur->rows + cur->nrows) == 1) &&
2116                         (cur->row_xpos + cur->start_char + 1) > row->length)
2117                               return E_REQUEST_DENIED;
2118 
2119                     if ((cur->rows + cur->nrows) == 1) {
2120                               cur->row_xpos++;
2121                               _formi_set_cursor_xpos(cur, (c == REQ_RIGHT_CHAR));
2122                     } else {
2123                               if (cur->cursor_xpos >= (row->expanded - 1)) {
2124                                         if ((row->next == NULL) ||
2125                                             (c == REQ_RIGHT_CHAR))
2126                                                   return E_REQUEST_DENIED;
2127 
2128                                         cur->cursor_xpos = 0;
2129                                         cur->row_xpos = 0;
2130                                         cur->cur_line = cur->cur_line->next;
2131                                         if (cur->cursor_ypos == (cur->rows - 1))
2132                                                   cur->start_line =
2133                                                             cur->start_line->next;
2134                                         else
2135                                                   cur->cursor_ypos++;
2136                               } else {
2137                                         old_xpos = cur->cursor_xpos;
2138                                         old_row_pos = cur->row_xpos;
2139                                         if (row->string[cur->row_xpos] == '\t')
2140                                                   cur->cursor_xpos += tab_size(row,
2141                                                                       cur->row_xpos);
2142                                         else
2143                                                   cur->cursor_xpos++;
2144                                         cur->row_xpos++;
2145                                         if (cur->cursor_xpos
2146                                             >= row->expanded) {
2147                                                   if ((row->next == NULL) ||
2148                                                       (c == REQ_RIGHT_CHAR)) {
2149                                                             cur->cursor_xpos = old_xpos;
2150                                                             cur->row_xpos = old_row_pos;
2151                                                             return E_REQUEST_DENIED;
2152                                                   }
2153 
2154                                                   cur->cursor_xpos = 0;
2155                                                   cur->row_xpos = 0;
2156                                                   cur->cur_line = cur->cur_line->next;
2157                                                   if (cur->cursor_ypos
2158                                                       == (cur->rows - 1))
2159                                                             cur->start_line =
2160                                                                       cur->start_line->next;
2161                                                   else
2162                                                             cur->cursor_ypos++;
2163                                         }
2164                               }
2165                     }
2166 
2167                     break;
2168 
2169           case REQ_LEFT_CHAR:
2170                       /*
2171                        * The behaviour of left_char is the same as prev_char
2172                        * except that the cursor will not wrap if it has
2173                        * reached the LHS of the field, so just check this
2174                        * and fall through if we are not at the LHS.
2175                        */
2176                     if (cur->cursor_xpos == 0)
2177                               return E_REQUEST_DENIED;
2178 
2179                       /* FALLTHRU */
2180           case REQ_PREV_CHAR:
2181                     if ((cur->rows + cur->nrows) == 1) {
2182                               if (cur->row_xpos == 0) {
2183                                         if (cur->start_char > 0)
2184                                                   cur->start_char--;
2185                                         else
2186                                                   return E_REQUEST_DENIED;
2187                               } else {
2188                                         cur->row_xpos--;
2189                                         _formi_set_cursor_xpos(cur, FALSE);
2190                               }
2191                     } else {
2192                               if ((cur->cursor_xpos == 0) &&
2193                                   (cur->cursor_ypos == 0) &&
2194                                   (cur->start_line->prev == NULL))
2195                                         return E_REQUEST_DENIED;
2196 
2197                               pos = cur->row_xpos;
2198                               if (cur->cursor_xpos > 0) {
2199                                         if (row->string[pos] == '\t') {
2200                                                   size = tab_size(row, pos);
2201                                                   if (size > cur->cursor_xpos) {
2202                                                             cur->cursor_xpos = 0;
2203                                                             cur->row_xpos = 0;
2204                                                   } else {
2205                                                             cur->row_xpos--;
2206                                                             cur->cursor_xpos -= size;
2207                                                   }
2208                                         } else {
2209                                                   cur->cursor_xpos--;
2210                                                   cur->row_xpos--;
2211                                         }
2212                               } else {
2213                                         cur->cur_line = cur->cur_line->prev;
2214                                         if (cur->cursor_ypos > 0)
2215                                                   cur->cursor_ypos--;
2216                                         else
2217                                                   cur->start_line =
2218                                                             cur->start_line->prev;
2219                                         row = cur->cur_line;
2220                                         if (row->expanded > 0) {
2221                                                   cur->cursor_xpos = row->expanded - 1;
2222                                         } else {
2223                                                   cur->cursor_xpos = 0;
2224                                         }
2225 
2226                                         if (row->length > 0)
2227                                                   cur->row_xpos = row->length - 1;
2228                                         else
2229                                                   cur->row_xpos = 0;
2230                               }
2231                     }
2232 
2233                     break;
2234 
2235           case REQ_DOWN_CHAR:
2236                       /*
2237                        * The down_char request has the same functionality as
2238                        * the next_line request excepting that the field is not
2239                        * scrolled if the cursor is at the bottom of the field.
2240                        * Check to see if the cursor is at the bottom of the field
2241                        * and if it is then deny the request otherwise fall
2242                        * through to the next_line handler.
2243                        */
2244                     if (cur->cursor_ypos >= cur->rows - 1)
2245                               return E_REQUEST_DENIED;
2246 
2247                       /* FALLTHRU */
2248 
2249           case REQ_NEXT_LINE:
2250                     if ((row->next == NULL) || (cur->cur_line->next == NULL))
2251                               return E_REQUEST_DENIED;
2252 
2253                     cur->cur_line = cur->cur_line->next;
2254                     if ((cur->cursor_ypos + 1) >= cur->rows) {
2255                               cur->start_line = cur->start_line->next;
2256                     } else
2257                               cur->cursor_ypos++;
2258                     row = cur->cur_line;
2259 
2260                     if (row->length == 0) {
2261                               cur->row_xpos = 0;
2262                               cur->cursor_xpos = 0;
2263                     } else {
2264                               if (cur->cursor_xpos > (row->expanded - 1))
2265                                         cur->cursor_xpos = row->expanded - 1;
2266 
2267                               cur->row_xpos =     tab_fit_len(row, cur->cursor_xpos + 1);
2268                               if (cur->row_xpos == 0)
2269                                         cur->cursor_xpos = 0;
2270                               else
2271                                         cur->cursor_xpos =
2272                                                   _formi_tab_expanded_length(
2273                                                             row->string, 0, cur->row_xpos);
2274                               if (cur->cursor_xpos > 0)
2275                                         cur->cursor_xpos--;
2276                     }
2277                     break;
2278 
2279           case REQ_UP_CHAR:
2280                       /*
2281                        * The up_char request has the same functionality as
2282                        * the prev_line request excepting the field is not
2283                        * scrolled, check if the cursor is at the top of the
2284                        * field, if it is deny the request otherwise fall
2285                        * through to the prev_line handler.
2286                        */
2287                     if (cur->cursor_ypos == 0)
2288                               return E_REQUEST_DENIED;
2289 
2290                       /* FALLTHRU */
2291 
2292           case REQ_PREV_LINE:
2293                     if (cur->cur_line->prev == NULL)
2294                               return E_REQUEST_DENIED;
2295 
2296                     if (cur->cursor_ypos == 0) {
2297                               if (cur->start_line->prev == NULL)
2298                                         return E_REQUEST_DENIED;
2299                               cur->start_line = cur->start_line->prev;
2300                     } else
2301                               cur->cursor_ypos--;
2302 
2303                     cur->cur_line = cur->cur_line->prev;
2304                     row = cur->cur_line;
2305 
2306                     if (row->length == 0) {
2307                               cur->row_xpos = 0;
2308                               cur->cursor_xpos = 0;
2309                     } else {
2310                               if (cur->cursor_xpos > (row->expanded - 1))
2311                                         cur->cursor_xpos = row->expanded - 1;
2312 
2313                               cur->row_xpos =     tab_fit_len(row, cur->cursor_xpos + 1);
2314                               cur->cursor_xpos =
2315                                         _formi_tab_expanded_length(row->string,
2316                                                                          0, cur->row_xpos);
2317                               if (cur->cursor_xpos > 0)
2318                                         cur->cursor_xpos--;
2319                     }
2320                     break;
2321 
2322           case REQ_NEXT_WORD:
2323                     start = cur->row_xpos + cur->start_char;
2324                     str = row->string;
2325 
2326                     wb = find_eow(cur, start, FALSE, &row);
2327                     if (wb < 0)
2328                               return wb;
2329 
2330                     start = wb;
2331                       /* check if we hit the end */
2332                     if (str[start] == '\0')
2333                               return E_REQUEST_DENIED;
2334 
2335                       /* otherwise we must have found the start of a word...*/
2336                     if ((cur->rows + cur->nrows) == 1) {
2337                                 /* single line field */
2338                               size = _formi_tab_expanded_length(str,
2339                                         cur->start_char, start);
2340                               if (size < cur->cols) {
2341                                         cur->row_xpos = start - cur->start_char;
2342                               } else {
2343                                         cur->start_char = start;
2344                                         cur->row_xpos = 0;
2345                               }
2346                               _formi_set_cursor_xpos(cur, FALSE);
2347                     } else {
2348                                 /* multiline field */
2349                               cur->cur_line = row;
2350                               adjust_ypos(cur, row);
2351 
2352                               cur->row_xpos = start;
2353                               cur->cursor_xpos =
2354                                         _formi_tab_expanded_length(
2355                                                   row->string, 0, cur->row_xpos) - 1;
2356                     }
2357                     break;
2358 
2359           case REQ_PREV_WORD:
2360                     start = cur->start_char + cur->row_xpos;
2361                     if (cur->start_char > 0)
2362                               start--;
2363 
2364                     if ((start == 0) && (row->prev == NULL))
2365                               return E_REQUEST_DENIED;
2366 
2367                     if (start == 0) {
2368                               row = row->prev;
2369                               if (row->length > 0)
2370                                         start = row->length - 1;
2371                               else
2372                                         start = 0;
2373                     }
2374 
2375                     str = row->string;
2376 
2377                     start = find_sow(start, &row);
2378 
2379                     if ((cur->rows + cur->nrows) == 1) {
2380                                 /* single line field */
2381                               size = _formi_tab_expanded_length(str,
2382                                         cur->start_char, start);
2383 
2384                               if (start > cur->start_char) {
2385                                         cur->row_xpos = start - cur->start_char;
2386                               } else {
2387                                         cur->start_char = start;
2388                                         cur->row_xpos = 0;
2389                               }
2390                               _formi_set_cursor_xpos(cur, FALSE);
2391                     } else {
2392                                 /* multiline field */
2393                               cur->cur_line = row;
2394                               adjust_ypos(cur, row);
2395                               cur->row_xpos = start;
2396                               cur->cursor_xpos =
2397                                         _formi_tab_expanded_length(
2398                                                   row->string, 0,
2399                                                   cur->row_xpos) - 1;
2400                     }
2401 
2402                     break;
2403 
2404           case REQ_BEG_FIELD:
2405                     cur->start_char = 0;
2406                     while (cur->start_line->prev != NULL)
2407                               cur->start_line = cur->start_line->prev;
2408                     cur->cur_line = cur->start_line;
2409                     cur->row_xpos = 0;
2410                     _formi_init_field_xpos(cur);
2411                     cur->cursor_ypos = 0;
2412                     break;
2413 
2414           case REQ_BEG_LINE:
2415                     cur->row_xpos = 0;
2416                     _formi_init_field_xpos(cur);
2417                     cur->start_char = 0;
2418                     break;
2419 
2420           case REQ_END_FIELD:
2421                     while (cur->cur_line->next != NULL)
2422                               cur->cur_line = cur->cur_line->next;
2423 
2424                     if (cur->row_count > cur->rows) {
2425                               cur->start_line = cur->cur_line;
2426                               pos = cur->rows - 1;
2427                               while (pos > 0) {
2428                                         cur->start_line = cur->start_line->prev;
2429                                         pos--;
2430                               }
2431                               cur->cursor_ypos = cur->rows - 1;
2432                     } else {
2433                               cur->cursor_ypos = cur->row_count - 1;
2434                     }
2435 
2436                       /* we fall through here deliberately, we are on the
2437                        * correct row, now we need to get to the end of the
2438                        * line.
2439                        */
2440                       /* FALLTHRU */
2441 
2442           case REQ_END_LINE:
2443                     row = cur->cur_line;
2444 
2445                     if ((cur->rows + cur->nrows) == 1) {
2446                               if (row->expanded > cur->cols - 1) {
2447                                         if ((cur->opts & O_STATIC) != O_STATIC) {
2448                                                   cur->start_char = tab_fit_window(
2449                                                             cur, row->length,
2450                                                             cur->cols) + 1;
2451                                                   cur->row_xpos = row->length
2452                                                             - cur->start_char;
2453                                         } else {
2454                                                   cur->start_char = 0;
2455                                                   cur->row_xpos = cur->cols - 1;
2456                                         }
2457                               } else {
2458                                         cur->row_xpos = row->length + 1;
2459                                         cur->start_char = 0;
2460                               }
2461                               _formi_set_cursor_xpos(cur, FALSE);
2462                     } else {
2463                               cur->row_xpos = row->length - 1;
2464                               cur->cursor_xpos = row->expanded - 1;
2465                               if (row->next == NULL) {
2466                                         cur->row_xpos++;
2467                                         cur->cursor_xpos++;
2468                               }
2469                     }
2470                     break;
2471 
2472           case REQ_NEW_LINE:
2473                     start = cur->start_char       + cur->row_xpos;
2474                     if ((status = split_line(cur, TRUE, start, &row)) != E_OK)
2475                               return status;
2476                     cur->cur_line->hard_ret = TRUE;
2477                     cur->cursor_xpos = 0;
2478                     cur->row_xpos = 0;
2479                     break;
2480 
2481           case REQ_INS_CHAR:
2482                     if ((status = _formi_add_char(cur, cur->start_char
2483                                                         + cur->row_xpos,
2484                                                         cur->pad)) != E_OK)
2485                               return status;
2486                     break;
2487 
2488           case REQ_INS_LINE:
2489                     if ((status = split_line(cur, TRUE, 0, &row)) != E_OK)
2490                               return status;
2491                     cur->cur_line->hard_ret = TRUE;
2492                     break;
2493 
2494           case REQ_DEL_CHAR:
2495                     row = cur->cur_line;
2496                     start = cur->start_char + cur->row_xpos;
2497                     end = row->length - 1;
2498                     if ((start >= row->length) && (row->next == NULL))
2499                               return E_REQUEST_DENIED;
2500 
2501                     if ((start == row->length - 1) || (row->length == 0)) {
2502                               if ((cur->rows + cur->nrows) > 1) {
2503                                         /*
2504                                          * Firstly, check if the current line has
2505                                          * a hard return.  In this case we just
2506                                          * want to "delete" the hard return and
2507                                          * re-wrap the field.  The hard return
2508                                          * does not occupy a character space in
2509                                          * the buffer but we must make it appear
2510                                          * like it does for a deletion.
2511                                          */
2512                                         if (row->hard_ret == TRUE) {
2513                                                   row->hard_ret = FALSE;
2514                                                   if (_formi_join_line(cur, &row,
2515                                                                            JOIN_NEXT)
2516                                                       != E_OK) {
2517                                                             row->hard_ret = TRUE;
2518                                                             return 0;
2519                                                   } else {
2520                                                             return 1;
2521                                                   }
2522                                         }
2523 
2524                                         /*
2525                                          * If we have more than one row, join the
2526                                          * next row to make things easier unless
2527                                          * we are at the end of the string, in
2528                                          * that case the join would fail but we
2529                                          * really want to delete the last char
2530                                          * in the field.
2531                                          */
2532                                         if (row->next != NULL) {
2533                                                   if (_formi_join_line(cur, &row,
2534                                                                            JOIN_NEXT_NW)
2535                                                       != E_OK) {
2536                                                             return E_REQUEST_DENIED;
2537                                                   }
2538                                         }
2539                               }
2540                     }
2541 
2542                     saved = row->string[start];
2543                     memmove(&row->string[start], &row->string[start + 1],
2544                           (size_t) (end - start + 1));
2545                     row->string[end] = '\0';
2546                     row->length--;
2547                     if (row->length > 0)
2548                               row->expanded = _formi_tab_expanded_length(
2549                                         row->string, 0, row->length - 1);
2550                     else
2551                               row->expanded = 0;
2552 
2553                       /*
2554                        * recalculate tabs for a single line field, multiline
2555                        * fields will do this when the field is wrapped.
2556                        */
2557                     if ((cur->rows + cur->nrows) == 1)
2558                               _formi_calculate_tabs(row);
2559                       /*
2560                        * if we are at the end of the string then back the
2561                        * cursor pos up one to stick on the end of the line
2562                        */
2563                     if (start == row->length) {
2564                               if (row->length > 1) {
2565                                         if ((cur->rows + cur->nrows) == 1) {
2566                                                   pos = cur->row_xpos + cur->start_char;
2567                                                   cur->start_char =
2568                                                             tab_fit_window(
2569                                                                       cur,
2570                                                                       cur->start_char + cur->row_xpos,
2571                                                                       cur->cols);
2572                                                   cur->row_xpos = pos - cur->start_char
2573                                                             - 1;
2574                                                   _formi_set_cursor_xpos(cur, FALSE);
2575                                         } else {
2576                                                   if (cur->row_xpos == 0) {
2577                                                             if (row->next != NULL) {
2578                                                                       if (_formi_join_line(
2579                                                                                 cur, &row,
2580                                                                                 JOIN_PREV_NW)
2581                                                                           != E_OK) {
2582                                                                                 return E_REQUEST_DENIED;
2583                                                                       }
2584                                                             } else {
2585                                                                       if (cur->row_count > 1)
2586                                                                                 cur->row_count--;
2587                                                             }
2588 
2589                                                   }
2590 
2591                                                   cur->row_xpos = start - 1;
2592                                                   cur->cursor_xpos =
2593                                                             _formi_tab_expanded_length(
2594                                                                       row->string,
2595                                                                       0, cur->row_xpos - 1);
2596                                                   if ((cur->cursor_xpos > 0)
2597                                                       && (start != (row->expanded - 1)))
2598                                                             cur->cursor_xpos--;
2599                                         }
2600 
2601                                         start--;
2602                               } else {
2603                                         start = 0;
2604                                         cur->row_xpos = 0;
2605                                         _formi_init_field_xpos(cur);
2606                               }
2607                     }
2608 
2609                     if ((cur->rows + cur->nrows) > 1) {
2610                               if (_formi_wrap_field(cur, row) != E_OK) {
2611                                         memmove(&row->string[start + 1],
2612                                               &row->string[start],
2613                                               (size_t) (end - start));
2614                                         row->length++;
2615                                         row->string[start] = saved;
2616                                         _formi_wrap_field(cur, row);
2617                                         return E_REQUEST_DENIED;
2618                               }
2619                     }
2620                     break;
2621 
2622           case REQ_DEL_PREV:
2623                     if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
2624                         && (cur->start_line->prev == NULL)
2625                         && (cur->cursor_ypos == 0))
2626                                  return E_REQUEST_DENIED;
2627 
2628                     row = cur->cur_line;
2629                     start = cur->row_xpos + cur->start_char;
2630                     end = row->length - 1;
2631                     eat_char = TRUE;
2632 
2633                     if ((cur->start_char + cur->row_xpos) == 0) {
2634                               if (row->prev == NULL)
2635                                         return E_REQUEST_DENIED;
2636 
2637                                 /*
2638                                  * If we are a multiline field then check if
2639                                  * the line above has a hard return.  If it does
2640                                  * then just "eat" the hard return and re-wrap
2641                                  * the field.
2642                                  */
2643                               if (row->prev->hard_ret == TRUE) {
2644                                         row->prev->hard_ret = FALSE;
2645                                         if (_formi_join_line(cur, &row,
2646                                                                  JOIN_PREV) != E_OK) {
2647                                                   row->prev->hard_ret = TRUE;
2648                                                   return 0;
2649                                         }
2650 
2651                                         eat_char = FALSE;
2652                               } else {
2653                                         start = row->prev->length;
2654                                           /*
2655                                            * Join this line to the previous
2656                                            * one.
2657                                            */
2658                                         if (_formi_join_line(cur, &row,
2659                                                                  JOIN_PREV_NW) != E_OK) {
2660                                                   return 0;
2661                                         }
2662                                         end = row->length - 1;
2663                               }
2664                     }
2665 
2666                     if (eat_char == TRUE) {
2667                                 /*
2668                                  * eat a char from the buffer.  Normally we do
2669                                  * this unless we have deleted a "hard return"
2670                                  * in which case we just want to join the lines
2671                                  * without losing a char.
2672                                  */
2673                               saved = row->string[start - 1];
2674                               memmove(&row->string[start - 1], &row->string[start],
2675                                     (size_t) (end - start + 1));
2676                               row->length--;
2677                               row->string[row->length] = '\0';
2678                               row->expanded = _formi_tab_expanded_length(
2679                                         row->string, 0, row->length - 1);
2680                     }
2681 
2682                     if ((cur->rows + cur->nrows) == 1) {
2683                               _formi_calculate_tabs(row);
2684                               pos = cur->row_xpos + cur->start_char;
2685                               if (pos > 0)
2686                                         pos--;
2687                               cur->start_char =
2688                                         tab_fit_window(cur,
2689                                                          cur->start_char + cur->row_xpos,
2690                                                          cur->cols);
2691                               cur->row_xpos = pos - cur->start_char;
2692                               _formi_set_cursor_xpos(cur, FALSE);
2693                     } else {
2694                               if (eat_char == TRUE) {
2695                                         cur->row_xpos--;
2696                                         if (cur->row_xpos > 0)
2697                                                   cur->cursor_xpos =
2698                                                             _formi_tab_expanded_length(
2699                                                                       row->string, 0,
2700                                                                       cur->row_xpos - 1);
2701                                         else
2702                                                   cur->cursor_xpos = 0;
2703                               }
2704 
2705                               if ((_formi_wrap_field(cur, row) != E_OK)) {
2706                                         memmove(&row->string[start],
2707                                               &row->string[start - 1],
2708                                               (size_t) (end - start));
2709                                         row->length++;
2710                                         row->string[start - 1] = saved;
2711                                         row->string[row->length] = '\0';
2712                                         _formi_wrap_field(cur, row);
2713                                         return E_REQUEST_DENIED;
2714                               }
2715                     }
2716                     break;
2717 
2718           case REQ_DEL_LINE:
2719                     if (((cur->rows + cur->nrows) == 1) ||
2720                         (cur->row_count == 1)) {
2721                                 /* single line case */
2722                               row->length = 0;
2723                               row->expanded = row->length = 0;
2724                               cur->row_xpos = 0;
2725                               _formi_init_field_xpos(cur);
2726                               cur->cursor_ypos = 0;
2727                     } else {
2728                                 /* multiline field */
2729                               old_count = cur->row_count;
2730                               cur->row_count--;
2731                               if (cur->row_count == 0)
2732                                         cur->row_count = 1;
2733 
2734                               if (old_count == 1) {
2735                                         row->expanded = row->length = 0;
2736                                         cur->cursor_xpos = 0;
2737                                         cur->row_xpos = 0;
2738                                         cur->cursor_ypos = 0;
2739                               } else
2740                                         add_to_free(cur, row);
2741 
2742                               if (row->next == NULL) {
2743                                         if (cur->cursor_ypos == 0) {
2744                                                   if (cur->start_line->prev != NULL) {
2745                                                             cur->start_line =
2746                                                                       cur->start_line->prev;
2747                                                   }
2748                                         } else {
2749                                                   cur->cursor_ypos--;
2750                                         }
2751                               }
2752 
2753                               if (old_count > 1) {
2754                                         if (cur->cursor_xpos > row->expanded) {
2755                                                   cur->cursor_xpos = row->expanded - 1;
2756                                                   cur->row_xpos = row->length - 1;
2757                                         }
2758 
2759                                         cur->start_line = cur->alines;
2760                                         rs = cur->start_line;
2761                                         cur->cursor_ypos = 0;
2762                                         while (rs != row) {
2763                                                   if (cur->cursor_ypos < cur->rows)
2764                                                             cur->cursor_ypos++;
2765                                                   else
2766                                                             cur->start_line =
2767                                                                       cur->start_line->next;
2768                                                   rs = rs->next;
2769                                         }
2770                               }
2771                     }
2772                     break;
2773 
2774           case REQ_DEL_WORD:
2775                     start = cur->start_char + cur->row_xpos;
2776                     str = row->string;
2777 
2778                     wb = find_eow(cur, start, TRUE, &row);
2779                     if (wb < 0)
2780                               return wb;
2781 
2782                     end = wb;
2783 
2784                       /*
2785                        * If not at the start of a word then find the start,
2786                        * we cannot blindly call find_sow because this will
2787                        * skip back a word if we are already at the start of
2788                        * a word.
2789                        */
2790                     if ((start > 0)
2791                         && !(isblank((unsigned char)str[start - 1]) &&
2792                               !isblank((unsigned char)str[start])))
2793                               start = find_sow(start, &row);
2794                     str = row->string;
2795                       /* XXXX hmmmm what if start and end on diff rows? XXXX */
2796                     memmove(&str[start], &str[end],
2797                           (size_t) (row->length - end + 1));
2798                     len = end - start;
2799                     row->length -= len;
2800 
2801                     if ((cur->rows + cur->nrows) > 1) {
2802                               row = cur->start_line + cur->cursor_ypos;
2803                               if (row->next != NULL) {
2804                                         /*
2805                                          * if not on the last row we need to
2806                                          * join on the next row so the line
2807                                          * will be re-wrapped.
2808                                          */
2809                                         _formi_join_line(cur, &row, JOIN_NEXT_NW);
2810                               }
2811                               _formi_wrap_field(cur, row);
2812                               cur->row_xpos = start;
2813                               cur->cursor_xpos = _formi_tab_expanded_length(
2814                                         row->string, 0, cur->row_xpos);
2815                               if (cur->cursor_xpos > 0)
2816                                         cur->cursor_xpos--;
2817                     } else {
2818                               _formi_calculate_tabs(row);
2819                               cur->row_xpos = start - cur->start_char;
2820                               if (cur->row_xpos > 0)
2821                                         cur->row_xpos--;
2822                               _formi_set_cursor_xpos(cur, FALSE);
2823                     }
2824                     break;
2825 
2826           case REQ_CLR_EOL:
2827                     row->string[cur->row_xpos + 1] = '\0';
2828                     row->length = cur->row_xpos + 1;
2829                     row->expanded = cur->cursor_xpos + 1;
2830                     break;
2831 
2832           case REQ_CLR_EOF:
2833                     row = cur->cur_line->next;
2834                     while (row != NULL) {
2835                               rs = row->next;
2836                               add_to_free(cur, row);
2837                               row = rs;
2838                               cur->row_count--;
2839                     }
2840                     break;
2841 
2842           case REQ_CLR_FIELD:
2843                     row = cur->alines->next;
2844                     cur->cur_line = cur->alines;
2845                     cur->start_line = cur->alines;
2846 
2847                     while (row != NULL) {
2848                               rs = row->next;
2849                               add_to_free(cur, row);
2850                               row = rs;
2851                     }
2852 
2853                     cur->alines->string[0] = '\0';
2854                     cur->alines->length = 0;
2855                     cur->alines->expanded = 0;
2856                     cur->row_count = 1;
2857                     cur->cursor_ypos = 0;
2858                     cur->row_xpos = 0;
2859                     _formi_init_field_xpos(cur);
2860                     cur->start_char = 0;
2861                     break;
2862 
2863           case REQ_OVL_MODE:
2864                     cur->overlay = 1;
2865                     break;
2866 
2867           case REQ_INS_MODE:
2868                     cur->overlay = 0;
2869                     break;
2870 
2871           case REQ_SCR_FLINE:
2872                     _formi_scroll_fwd(cur, 1);
2873                     break;
2874 
2875           case REQ_SCR_BLINE:
2876                     _formi_scroll_back(cur, 1);
2877                     break;
2878 
2879           case REQ_SCR_FPAGE:
2880                     _formi_scroll_fwd(cur, cur->rows);
2881                     break;
2882 
2883           case REQ_SCR_BPAGE:
2884                     _formi_scroll_back(cur, cur->rows);
2885                     break;
2886 
2887           case REQ_SCR_FHPAGE:
2888                     _formi_scroll_fwd(cur, cur->rows / 2);
2889                     break;
2890 
2891           case REQ_SCR_BHPAGE:
2892                     _formi_scroll_back(cur, cur->rows / 2);
2893                     break;
2894 
2895           case REQ_SCR_FCHAR:
2896                     _formi_hscroll_fwd(cur, row, 1);
2897                     break;
2898 
2899           case REQ_SCR_BCHAR:
2900                     _formi_hscroll_back(cur, row, 1);
2901                     break;
2902 
2903           case REQ_SCR_HFLINE:
2904                     _formi_hscroll_fwd(cur, row, cur->cols);
2905                     break;
2906 
2907           case REQ_SCR_HBLINE:
2908                     _formi_hscroll_back(cur, row, cur->cols);
2909                     break;
2910 
2911           case REQ_SCR_HFHALF:
2912                     _formi_hscroll_fwd(cur, row, cur->cols / 2);
2913                     break;
2914 
2915           case REQ_SCR_HBHALF:
2916                     _formi_hscroll_back(cur, row, cur->cols / 2);
2917                     break;
2918 
2919           default:
2920                     return 0;
2921           }
2922 
2923           _formi_dbg_printf(
2924                "%s: cursor_xpos=%d, row_xpos=%d, start_char=%d, length=%d, "
2925                "allocated=%d\n", __func__, cur->cursor_xpos, cur->row_xpos,
2926                cur->start_char, cur->cur_line->length, cur->cur_line->allocated);
2927           _formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__,
2928               cur->start_line, cur->cursor_ypos);
2929           _formi_dbg_printf("%s: string=\"%s\"\n", __func__,
2930               cur->cur_line->string);
2931           assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX)
2932                     && (cur->cursor_xpos >= cur->row_xpos));
2933           return 1;
2934 }
2935 
2936 /*
2937  * Validate the given character by passing it to any type character
2938  * checking routines, if they exist.
2939  */
2940 int
_formi_validate_char(FIELD * field,char c)2941 _formi_validate_char(FIELD *field, char c)
2942 {
2943           int ret_val;
2944 
2945           if (field->type == NULL)
2946                     return E_OK;
2947 
2948           ret_val = E_INVALID_FIELD;
2949           _formi_do_char_validation(field, field->type, c, &ret_val);
2950 
2951           return ret_val;
2952 }
2953 
2954 
2955 /*
2956  * Perform the validation of the character, invoke all field_type validation
2957  * routines.  If the field is ok then update ret_val to E_OK otherwise
2958  * ret_val is not changed.
2959  */
2960 static void
_formi_do_char_validation(FIELD * field,FIELDTYPE * type,char c,int * ret_val)2961 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
2962 {
2963           if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2964                     _formi_do_char_validation(field, type->link->next, c, ret_val);
2965                     _formi_do_char_validation(field, type->link->prev, c, ret_val);
2966           } else {
2967                     if (type->char_check == NULL)
2968                               *ret_val = E_OK;
2969                     else {
2970                               if (type->char_check((int)(unsigned char) c,
2971                                                        field->args) == TRUE)
2972                                         *ret_val = E_OK;
2973                     }
2974           }
2975 }
2976 
2977 /*
2978  * Validate the current field.  If the field validation returns success then
2979  * return E_OK otherwise return E_INVALID_FIELD.
2980  *
2981  */
2982 int
_formi_validate_field(FORM * form)2983 _formi_validate_field(FORM *form)
2984 {
2985           FIELD *cur;
2986           int ret_val, count;
2987 
2988 
2989           if ((form == NULL) || (form->fields == NULL) ||
2990               (form->fields[0] == NULL))
2991                     return E_INVALID_FIELD;
2992 
2993           cur = form->fields[form->cur_field];
2994 
2995             /*
2996              * Sync the buffer if it has been modified so the field
2997              * validation routines can use it and because this is
2998              * the correct behaviour according to AT&T implementation.
2999              */
3000           if ((cur->buf0_status == TRUE)
3001               && ((ret_val = _formi_sync_buffer(cur)) != E_OK))
3002                               return ret_val;
3003 
3004             /*
3005              * If buffer is untouched then the string pointer may be
3006              * NULL, see if this is ok or not.
3007              */
3008           if (cur->buffers[0].string == NULL) {
3009                     if ((cur->opts & O_NULLOK) == O_NULLOK)
3010                               return E_OK;
3011                     else
3012                               return E_INVALID_FIELD;
3013           }
3014 
3015           count = _formi_skip_blanks(cur->buffers[0].string, 0);
3016 
3017             /* check if we have a null field, depending on the nullok flag
3018              * this may be acceptable or not....
3019              */
3020           if (cur->buffers[0].string[count] == '\0') {
3021                     if ((cur->opts & O_NULLOK) == O_NULLOK)
3022                               return E_OK;
3023                     else
3024                               return E_INVALID_FIELD;
3025           }
3026 
3027             /* check if an unmodified field is ok */
3028           if (cur->buf0_status == 0) {
3029                     if ((cur->opts & O_PASSOK) == O_PASSOK)
3030                               return E_OK;
3031                     else
3032                               return E_INVALID_FIELD;
3033           }
3034 
3035             /* if there is no type then just accept the field */
3036           if (cur->type == NULL)
3037                     return E_OK;
3038 
3039           ret_val = E_INVALID_FIELD;
3040           _formi_do_validation(cur, cur->type, &ret_val);
3041 
3042           return ret_val;
3043 }
3044 
3045 /*
3046  * Perform the validation of the field, invoke all field_type validation
3047  * routines.  If the field is ok then update ret_val to E_OK otherwise
3048  * ret_val is not changed.
3049  */
3050 static void
_formi_do_validation(FIELD * field,FIELDTYPE * type,int * ret_val)3051 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
3052 {
3053           if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
3054                     _formi_do_validation(field, type->link->next, ret_val);
3055                     _formi_do_validation(field, type->link->prev, ret_val);
3056           } else {
3057                     if (type->field_check == NULL)
3058                               *ret_val = E_OK;
3059                     else {
3060                               if (type->field_check(field, field_buffer(field, 0))
3061                                   == TRUE)
3062                                         *ret_val = E_OK;
3063                     }
3064           }
3065 }
3066 
3067 /*
3068  * Select the next/previous choice for the field, the driver command
3069  * selecting the direction will be passed in c.  Return 1 if a choice
3070  * selection succeeded, 0 otherwise.
3071  */
3072 int
_formi_field_choice(FORM * form,int c)3073 _formi_field_choice(FORM *form, int c)
3074 {
3075           FIELDTYPE *type;
3076           FIELD *field;
3077 
3078           if ((form == NULL) || (form->fields == NULL) ||
3079               (form->fields[0] == NULL) ||
3080               (form->fields[form->cur_field]->type == NULL))
3081                     return 0;
3082 
3083           field = form->fields[form->cur_field];
3084           type = field->type;
3085 
3086           switch (c) {
3087           case REQ_NEXT_CHOICE:
3088                     if (type->next_choice == NULL)
3089                               return 0;
3090                     else
3091                               return type->next_choice(field,
3092                                                              field_buffer(field, 0));
3093 
3094           case REQ_PREV_CHOICE:
3095                     if (type->prev_choice == NULL)
3096                               return 0;
3097                     else
3098                               return type->prev_choice(field,
3099                                                              field_buffer(field, 0));
3100 
3101           default: /* should never happen! */
3102                     return 0;
3103           }
3104 }
3105 
3106 /*
3107  * Update the fields if they have changed.  The parameter old has the
3108  * previous current field as the current field may have been updated by
3109  * the driver.  Return 1 if the form page needs updating.
3110  *
3111  */
3112 int
_formi_update_field(FORM * form,int old_field)3113 _formi_update_field(FORM *form, int old_field)
3114 {
3115           int cur, i;
3116 
3117           cur = form->cur_field;
3118 
3119           if (old_field != cur) {
3120                     if (!((cur >= form->page_starts[form->page].first) &&
3121                           (cur <= form->page_starts[form->page].last))) {
3122                                 /* not on same page any more */
3123                               for (i = 0; i < form->max_page; i++) {
3124                                         if ((form->page_starts[i].in_use == 1) &&
3125                                             (form->page_starts[i].first <= cur) &&
3126                                             (form->page_starts[i].last >= cur)) {
3127                                                   form->page = i;
3128                                                   return 1;
3129                                         }
3130                               }
3131                     }
3132           }
3133 
3134           _formi_redraw_field(form, old_field);
3135           _formi_redraw_field(form, form->cur_field);
3136           return 0;
3137 }
3138 
3139 /*
3140  * Compare function for the field sorting
3141  *
3142  */
3143 static int
field_sort_compare(const void * one,const void * two)3144 field_sort_compare(const void *one, const void *two)
3145 {
3146           const FIELD *a, *b;
3147           int tl;
3148 
3149           a = *(const FIELD **) __UNCONST(one);
3150           b = *(const FIELD **) __UNCONST(two);
3151 
3152           if (a == NULL)
3153                     return 1;
3154 
3155           if (b == NULL)
3156                     return -1;
3157 
3158             /*
3159              * First check the page, we want the fields sorted by page.
3160              *
3161              */
3162           if (a->page != b->page)
3163                     return ((a->page > b->page)? 1 : -1);
3164 
3165           tl = _formi_top_left(a->parent, a->index, b->index);
3166 
3167             /*
3168              * sort fields left to right, top to bottom so the top left is
3169              * the lesser value....
3170              */
3171           return ((tl == a->index)? -1 : 1);
3172 }
3173 
3174 /*
3175  * Sort the fields in a form ready for driver traversal.
3176  */
3177 void
_formi_sort_fields(FORM * form)3178 _formi_sort_fields(FORM *form)
3179 {
3180           FIELD **sort_area;
3181           int i;
3182 
3183           TAILQ_INIT(&form->sorted_fields);
3184 
3185           if ((sort_area = malloc(sizeof(*sort_area) * form->field_count))
3186               == NULL)
3187                     return;
3188 
3189           memcpy(sort_area, form->fields, sizeof(*sort_area) * form->field_count);
3190           qsort(sort_area, (size_t) form->field_count, sizeof(FIELD *),
3191                 field_sort_compare);
3192 
3193           for (i = 0; i < form->field_count; i++)
3194                     TAILQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
3195 
3196           free(sort_area);
3197 }
3198 
3199 /*
3200  * Set the neighbours for all the fields in the given form.
3201  */
3202 void
_formi_stitch_fields(FORM * form)3203 _formi_stitch_fields(FORM *form)
3204 {
3205           int above_row, below_row, end_above, end_below, cur_row, real_end;
3206           FIELD *cur, *above, *below;
3207 
3208             /*
3209              * check if the sorted fields circle queue is empty, just
3210              * return if it is.
3211              */
3212           if (TAILQ_EMPTY(&form->sorted_fields))
3213                     return;
3214 
3215             /* initially nothing is above..... */
3216           above_row = -1;
3217           end_above = TRUE;
3218           above = NULL;
3219 
3220             /* set up the first field as the current... */
3221           cur = TAILQ_FIRST(&form->sorted_fields);
3222           cur_row = cur->form_row;
3223 
3224             /* find the first field on the next row if any */
3225           below = TAILQ_NEXT(cur, glue);
3226           below_row = -1;
3227           end_below = TRUE;
3228           real_end = TRUE;
3229           while (below != NULL) {
3230                     if (below->form_row != cur_row) {
3231                               below_row = below->form_row;
3232                               end_below = FALSE;
3233                               real_end = FALSE;
3234                               break;
3235                     }
3236                     below = TAILQ_NEXT(below, glue);
3237           }
3238 
3239             /* walk the sorted fields, setting the neighbour pointers */
3240           while (cur != NULL) {
3241                     if (cur == TAILQ_FIRST(&form->sorted_fields))
3242                               cur->left = NULL;
3243                     else
3244                               cur->left = TAILQ_PREV(cur, _formi_sort_head, glue);
3245 
3246                     if (cur == TAILQ_LAST(&form->sorted_fields, _formi_sort_head))
3247                               cur->right = NULL;
3248                     else
3249                               cur->right = TAILQ_NEXT(cur, glue);
3250 
3251                     if (end_above == TRUE)
3252                               cur->up = NULL;
3253                     else {
3254                               cur->up = above;
3255                               above = TAILQ_NEXT(above, glue);
3256                               if (above_row != above->form_row) {
3257                                         end_above = TRUE;
3258                                         above_row = above->form_row;
3259                               }
3260                     }
3261 
3262                     if (end_below == TRUE)
3263                               cur->down = NULL;
3264                     else {
3265                               cur->down = below;
3266                               below = TAILQ_NEXT(below, glue);
3267                               if (below == NULL) {
3268                                         end_below = TRUE;
3269                                         real_end = TRUE;
3270                               } else if (below_row != below->form_row) {
3271                                         end_below = TRUE;
3272                                         below_row = below->form_row;
3273                               }
3274                     }
3275 
3276                     cur = TAILQ_NEXT(cur, glue);
3277                     if ((cur != NULL)
3278                         && (cur_row != cur->form_row)) {
3279                               cur_row = cur->form_row;
3280                               if (end_above == FALSE) {
3281                                         for (; above !=
3282                                             TAILQ_FIRST(&form->sorted_fields);
3283                                             above = TAILQ_NEXT(above, glue)) {
3284                                                   if (above->form_row != above_row) {
3285                                                             above_row = above->form_row;
3286                                                             break;
3287                                                   }
3288                                         }
3289                               } else if (above == NULL) {
3290                                         above = TAILQ_FIRST(&form->sorted_fields);
3291                                         end_above = FALSE;
3292                                         above_row = above->form_row;
3293                               } else
3294                                         end_above = FALSE;
3295 
3296                               if (end_below == FALSE) {
3297                                         while (below_row == below->form_row) {
3298                                                   below = TAILQ_NEXT(below, glue);
3299                                                   if (below == NULL) {
3300                                                             real_end = TRUE;
3301                                                             end_below = TRUE;
3302                                                             break;
3303                                                   }
3304                                         }
3305 
3306                                         if (below != NULL)
3307                                                   below_row = below->form_row;
3308                               } else if (real_end == FALSE)
3309                                         end_below = FALSE;
3310 
3311                     }
3312           }
3313 }
3314 
3315 /*
3316  * Calculate the length of the displayed line allowing for any tab
3317  * characters that need to be expanded.  We assume that the tab stops
3318  * are 8 characters apart.  The parameters start and end are the
3319  * character positions in the string str we want to get the length of,
3320  * the function returns the number of characters from the start
3321  * position to the end position that should be displayed after any
3322  * intervening tabs have been expanded.
3323  */
3324 int
_formi_tab_expanded_length(char * str,unsigned int start,unsigned int end)3325 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end)
3326 {
3327           int len, start_len, i;
3328 
3329             /* if we have a null string then there is no length */
3330           if (str[0] == '\0')
3331                     return 0;
3332 
3333           len = 0;
3334           start_len = 0;
3335 
3336             /*
3337              * preceding tabs affect the length tabs in the span, so
3338              * we need to calculate the length including the stuff before
3339              * start and then subtract off the unwanted bit.
3340              */
3341           for (i = 0; i <= end; i++) {
3342                     if (i == start) /* stash preamble length for later */
3343                               start_len = len;
3344 
3345                     if (str[i] == '\0')
3346                               break;
3347 
3348                     if (str[i] == '\t')
3349                               len = len - (len % 8) + 8;
3350                     else
3351                               len++;
3352           }
3353 
3354           _formi_dbg_printf(
3355               "%s: start=%d, end=%d, expanded=%d (diff=%d)\n", __func__,
3356               start, end, (len - start_len), (end - start));
3357 
3358           return (len - start_len);
3359 }
3360 
3361 /*
3362  * Calculate the tab stops on a given line in the field and set up
3363  * the tabs list with the results.  We do this by scanning the line for tab
3364  * characters and if one is found, noting the position and the number of
3365  * characters to get to the next tab stop.  This information is kept to
3366  * make manipulating the field (scrolling and so on) easier to handle.
3367  */
3368 void
_formi_calculate_tabs(_FORMI_FIELD_LINES * row)3369 _formi_calculate_tabs(_FORMI_FIELD_LINES *row)
3370 {
3371           _formi_tab_t *ts = row->tabs, *old_ts = NULL, **tsp;
3372           int i, j;
3373 
3374             /*
3375              * If the line already has tabs then invalidate them by
3376              * walking the list and killing the in_use flag.
3377              */
3378           for (; ts != NULL; ts = ts->fwd)
3379                     ts->in_use = FALSE;
3380 
3381 
3382             /*
3383              * Now look for tabs in the row and record the info...
3384              */
3385           tsp = &row->tabs;
3386           for (i = 0, j = 0; i < row->length; i++, j++) {
3387                     if (row->string[i] == '\t') {
3388                               if (*tsp == NULL) {
3389                                         if ((*tsp = malloc(sizeof(**tsp))) == NULL)
3390                                                   return;
3391                                         (*tsp)->back = old_ts;
3392                                         (*tsp)->fwd = NULL;
3393                               }
3394 
3395                               (*tsp)->in_use = TRUE;
3396                               (*tsp)->pos = i;
3397                               (*tsp)->size = 8 - (j % 8);
3398                               j += (*tsp)->size - 1;
3399                               old_ts = *tsp;
3400                               tsp = &(*tsp)->fwd;
3401                     }
3402           }
3403 }
3404 
3405 /*
3406  * Return the size of the tab padding for a tab character at the given
3407  * position.  Return 1 if there is not a tab char entry matching the
3408  * given location.
3409  */
3410 static int
tab_size(_FORMI_FIELD_LINES * row,unsigned int i)3411 tab_size(_FORMI_FIELD_LINES *row, unsigned int i)
3412 {
3413           _formi_tab_t *ts;
3414 
3415           ts = row->tabs;
3416           while ((ts != NULL) && (ts->pos != i))
3417                     ts = ts->fwd;
3418 
3419           if (ts == NULL)
3420                     return 1;
3421           else
3422                     return ts->size;
3423 }
3424 
3425 /*
3426  * Find the character offset that corresponds to longest tab expanded
3427  * string that will fit into the given window.  Walk the string backwards
3428  * evaluating the sizes of any tabs that are in the string.  Note that
3429  * using this function on a multi-line window will produce undefined
3430  * results - it is really only required for a single row field.
3431  */
3432 static int
tab_fit_window(FIELD * field,unsigned int pos,unsigned int window)3433 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window)
3434 {
3435           int scroll_amt, i;
3436           _formi_tab_t *ts;
3437 
3438             /* first find the last tab */
3439           ts = field->alines->tabs;
3440 
3441             /*
3442              * unless there are no tabs - just return the window size,
3443              * if there is enough room, otherwise 0.
3444              */
3445           if (ts == NULL) {
3446                     if (field->alines->length < window)
3447                               return 0;
3448                     else
3449                               return field->alines->length - window + 1;
3450           }
3451 
3452           while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE))
3453                     ts = ts->fwd;
3454 
3455             /*
3456              * now walk backwards finding the first tab that is to the
3457              * left of our starting pos.
3458              */
3459           while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos))
3460                     ts = ts->back;
3461 
3462           scroll_amt = 0;
3463           for (i = pos; i >= 0; i--) {
3464                     if (field->alines->string[i] == '\t') {
3465                               assert((ts != NULL) && (ts->in_use == TRUE));
3466                               if (ts->pos == i) {
3467                                         if ((scroll_amt + ts->size) > window) {
3468                                                   break;
3469                                         }
3470                                         scroll_amt += ts->size;
3471                                         ts = ts->back;
3472                               }
3473                               else
3474                                         assert(ts->pos == i);
3475                     } else {
3476                               scroll_amt++;
3477                               if (scroll_amt > window)
3478                                         break;
3479                     }
3480           }
3481 
3482           return ++i;
3483 }
3484 
3485 /*
3486  * Return the position of the last character that will fit into the
3487  * given width after tabs have been expanded for a given row of a given
3488  * field.
3489  */
3490 static unsigned int
tab_fit_len(_FORMI_FIELD_LINES * row,unsigned int width)3491 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int width)
3492 {
3493           unsigned int pos, len, row_pos;
3494           _formi_tab_t *ts;
3495 
3496           ts = row->tabs;
3497           pos = 0;
3498           len = 0;
3499           row_pos = 0;
3500 
3501           if (width == 0)
3502                     return 0;
3503 
3504           while ((len < width) && (pos < row->length)) {
3505                     if (row->string[pos] == '\t') {
3506                               assert((ts != NULL) && (ts->in_use == TRUE));
3507                               if (ts->pos == row_pos) {
3508                                         if ((len + ts->size) > width)
3509                                                   break;
3510                                         len += ts->size;
3511                                         ts = ts->fwd;
3512                               }
3513                               else
3514                                         assert(ts->pos == row_pos);
3515                     } else
3516                               len++;
3517                     pos++;
3518                     row_pos++;
3519           }
3520 
3521           if (pos > 0)
3522                     pos--;
3523           return pos;
3524 }
3525 
3526 /*
3527  * Sync the field line structures with the contents of buffer 0 for that
3528  * field.  We do this by walking all the line structures and concatenating
3529  * all the strings into one single string in buffer 0.
3530  */
3531 int
_formi_sync_buffer(FIELD * field)3532 _formi_sync_buffer(FIELD *field)
3533 {
3534           _FORMI_FIELD_LINES *line;
3535           char *nstr, *tmp;
3536           unsigned length;
3537 
3538           if (field->alines == NULL)
3539                     return E_BAD_ARGUMENT;
3540 
3541           if (field->alines->string == NULL)
3542                     return E_BAD_ARGUMENT;
3543 
3544             /*
3545              * init nstr up front, just in case there are no line contents,
3546              * this could happen if the field just contains hard returns.
3547              */
3548           if ((nstr = malloc(sizeof(*nstr))) == NULL)
3549                     return E_SYSTEM_ERROR;
3550           nstr[0] = '\0';
3551 
3552           line = field->alines;
3553           length = 1; /* allow for terminating null */
3554 
3555           while (line != NULL) {
3556                     if (line->length != 0) {
3557                               if ((tmp = realloc(nstr,
3558                                                      (size_t) (length + line->length)))
3559                                   == NULL) {
3560                                         if (nstr != NULL)
3561                                                   free(nstr);
3562                                         return (E_SYSTEM_ERROR);
3563                               }
3564 
3565                               nstr = tmp;
3566                               strcat(nstr, line->string);
3567                               length += line->length;
3568                     }
3569 
3570                     line = line->next;
3571           }
3572 
3573           if (field->buffers[0].string != NULL)
3574                     free(field->buffers[0].string);
3575           field->buffers[0].allocated = length;
3576           field->buffers[0].length = length - 1;
3577           field->buffers[0].string = nstr;
3578           return E_OK;
3579 }
3580 
3581 
3582 
3583