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