Expand PMF_FN_* macros.
[netbsd-mini2440.git] / lib / libform / internals.c
blob0333cf2d6a67580fbe657a57837e49b403fb84b0
1 /* $NetBSD: internals.c,v 1.31 2006/03/19 20:08:09 christos Exp $ */
3 /*-
4 * Copyright (c) 1998-1999 Brett Lymn
5 * (blymn@baea.com.au, brett_lymn@yahoo.com.au)
6 * All rights reserved.
8 * This code has been donated to The NetBSD Foundation by the Author.
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
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.
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: internals.c,v 1.31 2006/03/19 20:08:09 christos Exp $");
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"
44 #ifdef DEBUG
46 * file handle to write debug info to, this will be initialised when
47 * the form is first posted.
49 FILE *dbg = NULL;
52 * map the request numbers to strings for debug
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
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++)
73 #define min(a,b) (((a) > (b))? (b) : (a))
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 */
81 /* for the bump_lines function... */
82 #define _FORMI_USE_CURRENT -1 /* indicates current cursor pos to be used */
84 /* used in add_char for initial memory allocation for string in row */
85 #define INITIAL_LINE_ALLOC 16
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);
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.
135 static void
136 adjust_ypos(FIELD *field, _FORMI_FIELD_LINES *line)
138 unsigned ypos;
139 _FORMI_FIELD_LINES *rs;
141 ypos = 0;
142 rs = field->lines;
143 while (rs != line) {
144 rs = rs->next;
145 ypos++;
148 field->cursor_ypos = ypos;
149 field->start_line = field->lines;
150 if (ypos > (field->rows - 1)) {
152 * cur_line off the end of the field,
153 * adjust start_line so fix this.
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;
166 * Delete the given row and add it to the free list of the given field.
168 static void
169 add_to_free(FIELD *field, _FORMI_FIELD_LINES *line)
171 _FORMI_FIELD_LINES *saved;
173 saved = line;
175 /* don't remove if only one line... */
176 if ((line->prev == NULL) && (line->next == NULL))
177 return;
179 if (line->prev == NULL) {
180 /* handle top of list */
181 field->lines = line->next;
182 field->lines->prev = NULL;
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;
204 saved->next = field->free;
205 field->free = saved;
206 saved->prev = NULL;
207 if (saved->next != NULL)
208 saved->next->prev = line;
212 * Duplicate the given row, return the pointer to the new copy or
213 * NULL if the copy fails.
215 static _FORMI_FIELD_LINES *
216 copy_row(_FORMI_FIELD_LINES *row)
218 _FORMI_FIELD_LINES *new;
219 _formi_tab_t *tp, *newt;
221 if ((new = (_FORMI_FIELD_LINES *) malloc(sizeof(_FORMI_FIELD_LINES)))
222 == NULL) {
223 return NULL;
226 memcpy(new, row, sizeof(_FORMI_FIELD_LINES));
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;
233 if ((new->string = (char *) malloc((size_t)new->allocated)) == NULL) {
234 free(new);
235 return NULL;
238 memcpy(new->string, row->string, (size_t) row->length + 1);
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;
249 memcpy(new->tabs, row->tabs, sizeof(_formi_tab_t));
250 new->tabs->back = NULL;
251 new->tabs->fwd = NULL;
253 tp = tp->fwd;
254 newt = new->tabs;
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;
268 free(new->string);
269 free(new);
270 return NULL;
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;
281 return new;
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.
290 void
291 _formi_init_field_xpos(FIELD *field)
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;
300 switch (field->justification) {
301 case JUSTIFY_RIGHT:
302 field->cursor_xpos = field->cols - 1;
303 break;
305 case JUSTIFY_CENTER:
306 field->cursor_xpos = (field->cols - 1) / 2;
307 break;
309 default: /* assume left justify */
310 field->cursor_xpos = 0;
311 break;
317 * Open the debug file if it is not already open....
319 #ifdef DEBUG
321 _formi_create_dbg_file(void)
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;
331 return E_OK;
333 #endif
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.
345 static bool
346 check_field_size(FIELD *field)
348 if ((field->opts & O_STATIC) != O_STATIC) {
349 /* dynamic field */
350 if (field->max == 0) /* unlimited */
351 return TRUE;
353 if (field->rows == 1) {
354 return (field->lines->length < field->max);
355 } else {
356 return (field->row_count <= field->max);
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));
369 * Set the form's current field to the first valid field on the page.
370 * Assume the fields have been sorted and stitched.
373 _formi_pos_first_field(FORM *form)
375 FIELD *cur;
376 int old_page;
378 old_page = form->page;
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;
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;
400 form->cur_field = cur->index;
401 return E_OK;
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.
411 _formi_pos_new_field(FORM *form, unsigned direction, unsigned use_sorted)
413 FIELD *cur;
414 int i;
416 i = form->cur_field;
417 cur = form->fields[i];
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;
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;
451 if ((form->fields[i]->opts & (O_VISIBLE | O_ACTIVE))
452 == (O_VISIBLE | O_ACTIVE)) {
453 form->cur_field = i;
454 return E_OK;
457 while (i != form->cur_field);
459 return E_REQUEST_DENIED;
463 * Destroy the list of line structs passed by freeing all allocated
464 * memory.
466 static void
467 destroy_row_list(_FORMI_FIELD_LINES *start)
469 _FORMI_FIELD_LINES *temp, *row;
470 _formi_tab_t *tt, *tp;
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;
484 if (row->string != NULL)
485 free(row->string);
487 temp = row->next;
488 free(row);
489 row = temp;
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.
500 _formi_wrap_field(FIELD *field, _FORMI_FIELD_LINES *loc)
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;
508 if ((field->opts & O_STATIC) == O_STATIC) {
509 if ((field->rows + field->nrows) == 1) {
510 return E_OK; /* cannot wrap a single line */
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;
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.
525 if (field->rows > 1) {
526 width = field->cols;
527 } else {
528 return E_OK;
532 row = loc;
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.
538 if (row->prev != NULL)
539 row = row->prev;
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;
548 * Save a copy of the lines affected, just in case things
549 * don't work out.
551 if ((row_backup = copy_row(row)) == NULL)
552 return E_SYSTEM_ERROR;
554 temp = row_backup;
555 row = row->next;
557 saved_cur_line = temp;
558 saved_start_line = temp;
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;
567 temp->next->prev = temp;
568 temp = temp->next;
570 if (row == field->start_line)
571 saved_start_line = temp;
572 if (row == field->cur_line)
573 saved_cur_line = temp;
575 row = row->next;
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)) {
585 * Skip the line if it has a hard return
586 * and it is not the last, we cannot join
587 * anything to it.
589 row = row->next;
590 continue;
593 if ((row->next == NULL)) {
595 * If there are no more lines and this line
596 * is too short then our job is over.
598 break;
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 */
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.
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);
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);
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)
632 if ((pos == 0)
633 || (!isblank(row->string[pos - 1]))) {
634 wrap_err = E_NO_ROOM;
635 goto restore_and_exit;
639 /* if we are at the end of the string and it has
640 * a trailing blank, don't wrap the blank.
642 if ((row->next == NULL) && (pos == row->length - 1) &&
643 (isblank(row->string[pos])) &&
644 row->expanded <= field->cols)
645 continue;
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.
653 if ((isblank(row->string[pos])) &&
654 (pos != row->length - 1))
655 pos++;
657 if (split_line(field, FALSE, pos, &row) != E_OK) {
658 wrap_err = E_REQUEST_DENIED;
659 goto restore_and_exit;
661 } else
662 /* line is exactly the right length, do next one */
663 row = row->next;
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))) {
671 wrap_err = E_REQUEST_DENIED;
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;
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;
688 destroy_row_list(saved_row);
689 return wrap_err;
692 destroy_row_list(row_backup);
693 return E_OK;
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.
704 static int
705 _formi_join_line(FIELD *field, _FORMI_FIELD_LINES **rowp, int direction)
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;
714 if (_formi_create_dbg_file() == E_OK) {
715 dbg_ok = TRUE;
718 if (dbg_ok == TRUE) {
719 fprintf(dbg, "join_line: working on row %p, row_count = %d\n",
720 row, field->row_count);
722 #endif
724 if ((direction == JOIN_NEXT) || (direction == JOIN_NEXT_NW)) {
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.
730 if ((row->next == NULL) || (row->hard_ret == TRUE)) {
731 return E_REQUEST_DENIED;
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);
743 #endif
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;
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;
764 _formi_calculate_tabs(row);
765 row->hard_ret = row->next->hard_ret;
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--;
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;
786 /* remove joined line record from the row list */
787 add_to_free(field, row->next);
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);
795 #endif
796 } else {
797 if (row->prev == NULL) {
798 return E_REQUEST_DENIED;
801 saved = row->prev;
804 * Don't try to join if the line above has a hard
805 * return on it.
807 if (saved->hard_ret == TRUE) {
808 return E_REQUEST_DENIED;
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);
820 #endif
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;
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;
842 saved->hard_ret = row->hard_ret;
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--;
855 add_to_free(field, row);
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);
863 #endif
864 row = saved;
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.
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;
882 /* now check if we need to adjust cursor_ypos */
883 if (field->cursor_ypos > count) {
884 field->cursor_ypos--;
887 field->row_count--;
888 *rowp = row;
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;
897 return E_OK;
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.
905 static int
906 split_line(FIELD *field, bool hard_split, unsigned pos,
907 _FORMI_FIELD_LINES **rowp)
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
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.
920 if ((pos == 0) && (hard_split == FALSE))
921 return E_OK;
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;
928 #endif
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;
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);
956 #endif
958 assert((row->length < INT_MAX) && (row->expanded < INT_MAX));
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;
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;
977 strcpy(new_line->string, &row->string[pos]);
979 row->length = pos;
980 row->string[pos] = '\0';
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);
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;
996 _formi_calculate_tabs(new_line);
999 * If the given row was the current line then adjust the
1000 * current line pointer if necessary
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--;
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;
1017 else
1018 assert(field->start_line->next == NULL);
1023 * If the line split had a hard return then replace the
1024 * current line's hard return with a soft return and carry
1025 * the hard return onto the line after.
1027 if (row->hard_ret == TRUE) {
1028 new_line->hard_ret = TRUE;
1029 row->hard_ret = FALSE;
1033 * except where we are doing a hard split then the current
1034 * row must have a hard return on it too...
1036 if (hard_split == TRUE) {
1037 row->hard_ret = TRUE;
1040 assert(((row->expanded < INT_MAX) &&
1041 (new_line->expanded < INT_MAX) &&
1042 (row->length < INT_MAX) &&
1043 (new_line->length < INT_MAX)));
1045 #ifdef DEBUG
1046 if (dbg_ok == TRUE) {
1047 fprintf(dbg, "split_line: exit: ");
1048 fprintf(dbg, "row.length = %d, row.expanded = %d, ",
1049 row->length, row->expanded);
1050 fprintf(dbg,
1051 "next_line.length = %d, next_line.expanded = %d, ",
1052 new_line->length, new_line->expanded);
1053 fprintf(dbg, "row_count = %d\n", field->row_count + 1);
1055 #endif
1057 field->row_count++;
1058 *rowp = new_line;
1060 return E_OK;
1064 * skip the blanks in the given string, start at the index start and
1065 * continue forward until either the end of the string or a non-blank
1066 * character is found. Return the index of either the end of the string or
1067 * the first non-blank character.
1069 unsigned
1070 _formi_skip_blanks(char *string, unsigned int start)
1072 unsigned int i;
1074 i = start;
1076 while ((string[i] != '\0') && isblank(string[i]))
1077 i++;
1079 return i;
1083 * Skip the blanks in the string associated with the given row, pass back
1084 * the row and the offset at which the first non-blank is found. If no
1085 * non-blank character is found then return the index to the last
1086 * character on the last line.
1089 unsigned
1090 field_skip_blanks(unsigned int start, _FORMI_FIELD_LINES **rowp)
1092 unsigned int i;
1093 _FORMI_FIELD_LINES *row, *last = NULL;
1095 row = *rowp;
1096 i = start;
1098 do {
1099 i = _formi_skip_blanks(&row->string[i], i);
1100 if (!isblank(row->string[i])) {
1101 last = row;
1102 row = row->next;
1104 * don't reset if last line otherwise we will
1105 * not be at the end of the string.
1107 if (row != NULL)
1108 i = 0;
1109 } else
1110 break;
1112 while (row != NULL);
1115 * If we hit the end of the row list then point at the last row
1116 * otherwise we return the row we found the blank on.
1118 if (row == NULL)
1119 *rowp = last;
1120 else
1121 *rowp = row;
1123 return i;
1127 * Return the index of the top left most field of the two given fields.
1129 static int
1130 _formi_top_left(FORM *form, int a, int b)
1132 /* lower row numbers always win here.... */
1133 if (form->fields[a]->form_row < form->fields[b]->form_row)
1134 return a;
1136 if (form->fields[a]->form_row > form->fields[b]->form_row)
1137 return b;
1139 /* rows must be equal, check columns */
1140 if (form->fields[a]->form_col < form->fields[b]->form_col)
1141 return a;
1143 if (form->fields[a]->form_col > form->fields[b]->form_col)
1144 return b;
1146 /* if we get here fields must be in exactly the same place, punt */
1147 return a;
1151 * Return the index to the field that is the bottom-right-most of the
1152 * two given fields.
1154 static int
1155 _formi_bottom_right(FORM *form, int a, int b)
1157 /* check the rows first, biggest row wins */
1158 if (form->fields[a]->form_row > form->fields[b]->form_row)
1159 return a;
1160 if (form->fields[a]->form_row < form->fields[b]->form_row)
1161 return b;
1163 /* rows must be equal, check cols, biggest wins */
1164 if (form->fields[a]->form_col > form->fields[b]->form_col)
1165 return a;
1166 if (form->fields[a]->form_col < form->fields[b]->form_col)
1167 return b;
1169 /* fields in the same place, punt */
1170 return a;
1174 * Find the end of the current word in the string str, starting at
1175 * offset - the end includes any trailing whitespace. If the end of
1176 * the string is found before a new word then just return the offset
1177 * to the end of the string. If do_join is TRUE then lines will be
1178 * joined (without wrapping) until either the end of the field or the
1179 * end of a word is found (whichever comes first).
1181 static int
1182 find_eow(FIELD *cur, unsigned int offset, bool do_join,
1183 _FORMI_FIELD_LINES **rowp)
1185 int start;
1186 _FORMI_FIELD_LINES *row;
1188 row = *rowp;
1189 start = offset;
1191 do {
1192 /* first skip any non-whitespace */
1193 while ((row->string[start] != '\0')
1194 && !isblank(row->string[start]))
1195 start++;
1197 /* see if we hit the end of the string */
1198 if (row->string[start] == '\0') {
1199 if (do_join == TRUE) {
1200 if (row->next == NULL)
1201 return start;
1203 if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1204 != E_OK)
1205 return E_REQUEST_DENIED;
1206 } else {
1207 do {
1208 if (row->next == NULL) {
1209 *rowp = row;
1210 return start;
1211 } else {
1212 row = row->next;
1213 start = 0;
1215 } while (row->length == 0);
1218 } while (!isblank(row->string[start]));
1220 do {
1221 /* otherwise skip the whitespace.... */
1222 while ((row->string[start] != '\0')
1223 && isblank(row->string[start]))
1224 start++;
1226 if (row->string[start] == '\0') {
1227 if (do_join == TRUE) {
1228 if (row->next == NULL)
1229 return start;
1231 if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1232 != E_OK)
1233 return E_REQUEST_DENIED;
1234 } else {
1235 do {
1236 if (row->next == NULL) {
1237 *rowp = row;
1238 return start;
1239 } else {
1240 row = row->next;
1241 start = 0;
1243 } while (row->length == 0);
1246 } while (isblank(row->string[start]));
1248 *rowp = row;
1249 return start;
1253 * Find the beginning of the current word in the string str, starting
1254 * at offset.
1256 static int
1257 find_sow(unsigned int offset, _FORMI_FIELD_LINES **rowp)
1259 int start;
1260 char *str;
1261 _FORMI_FIELD_LINES *row;
1263 row = *rowp;
1264 str = row->string;
1265 start = offset;
1267 do {
1268 if (start > 0) {
1269 if (isblank(str[start]) || isblank(str[start - 1])) {
1270 if (isblank(str[start - 1]))
1271 start--;
1272 /* skip the whitespace.... */
1273 while ((start >= 0) && isblank(str[start]))
1274 start--;
1278 /* see if we hit the start of the string */
1279 if (start < 0) {
1280 do {
1281 if (row->prev == NULL) {
1282 *rowp = row;
1283 start = 0;
1284 return start;
1285 } else {
1286 row = row->prev;
1287 str = row->string;
1288 if (row->length > 0)
1289 start = row->length - 1;
1290 else
1291 start = 0;
1293 } while (row->length == 0);
1295 } while (isblank(row->string[start]));
1297 /* see if we hit the start of the string */
1298 if (start < 0) {
1299 *rowp = row;
1300 return 0;
1303 /* now skip any non-whitespace */
1304 do {
1305 while ((start >= 0) && !isblank(str[start]))
1306 start--;
1309 if (start < 0) {
1310 do {
1311 if (row->prev == NULL) {
1312 *rowp = row;
1313 start = 0;
1314 return start;
1315 } else {
1316 row = row->prev;
1317 str = row->string;
1318 if (row->length > 0)
1319 start = row->length - 1;
1320 else
1321 start = 0;
1323 } while (row->length == 0);
1325 } while (!isblank(str[start]));
1327 if (start > 0) {
1328 start++; /* last loop has us pointing at a space, adjust */
1329 if (start >= row->length) {
1330 if (row->next != NULL) {
1331 start = 0;
1332 row = row->next;
1333 } else {
1334 start = row->length - 1;
1339 if (start < 0)
1340 start = 0;
1342 *rowp = row;
1343 return start;
1347 * Scroll the field forward the given number of lines.
1349 static void
1350 _formi_scroll_fwd(FIELD *field, unsigned int amt)
1352 unsigned int count;
1353 _FORMI_FIELD_LINES *end_row;
1355 end_row = field->start_line;
1356 /* walk the line structs forward to find the bottom of the field */
1357 count = field->rows - 1;
1358 while ((count > 0) && (end_row->next != NULL))
1360 count--;
1361 end_row = end_row->next;
1364 /* check if there are lines to scroll */
1365 if ((count > 0) && (end_row->next == NULL))
1366 return;
1369 * ok, lines to scroll - do this by walking both the start_line
1370 * and the end_row at the same time for amt lines, we stop when
1371 * either we have done the number of lines or end_row hits the
1372 * last line in the field.
1374 count = amt;
1375 while ((count > 0) && (end_row->next != NULL)) {
1376 count--;
1377 field->start_line = field->start_line->next;
1378 end_row = end_row->next;
1383 * Scroll the field backward the given number of lines.
1385 static void
1386 _formi_scroll_back(FIELD *field, unsigned int amt)
1388 unsigned int count;
1390 /* check for lines above */
1391 if (field->start_line->prev == NULL)
1392 return;
1395 * Backward scroll is easy, follow row struct chain backward until
1396 * the number of lines done or we reach the top of the field.
1398 count = amt;
1399 while ((count > 0) && (field->start_line->prev != NULL)) {
1400 count--;
1401 field->start_line = field->start_line->prev;
1406 * Scroll the field forward the given number of characters.
1408 void
1409 _formi_hscroll_fwd(FIELD *field, _FORMI_FIELD_LINES *row, int unsigned amt)
1411 unsigned int end, scroll_amt, expanded;
1412 _formi_tab_t *ts;
1415 if ((row->tabs == NULL) || (row->tabs->in_use == FALSE)) {
1416 /* if the line has no tabs things are easy... */
1417 end = field->start_char + field->cols + amt - 1;
1418 scroll_amt = amt;
1419 if (end > row->length) {
1420 end = row->length;
1421 scroll_amt = end - field->start_char - field->cols + 1;
1423 } else {
1425 * If there are tabs we need to add on the scroll amount,
1426 * find the last char position that will fit into
1427 * the field and finally fix up the start_char. This
1428 * is a lot of work but handling the case where there
1429 * are not enough chars to scroll by amt is difficult.
1431 end = field->start_char + field->row_xpos + amt;
1432 if (end >= row->length)
1433 end = row->length - 1;
1434 else {
1435 expanded = _formi_tab_expanded_length(
1436 row->string,
1437 field->start_char + amt,
1438 field->start_char + field->row_xpos + amt);
1439 ts = row->tabs;
1440 /* skip tabs to the lhs of our starting point */
1441 while ((ts != NULL) && (ts->in_use == TRUE)
1442 && (ts->pos < end))
1443 ts = ts->fwd;
1445 while ((expanded <= field->cols)
1446 && (end < row->length)) {
1447 if (row->string[end] == '\t') {
1448 assert((ts != NULL)
1449 && (ts->in_use == TRUE));
1450 if (ts->pos == end) {
1451 if ((expanded + ts->size)
1452 > field->cols)
1453 break;
1454 expanded += ts->size;
1455 ts = ts->fwd;
1457 else
1458 assert(ts->pos == end);
1459 } else
1460 expanded++;
1461 end++;
1465 scroll_amt = tab_fit_window(field, end, field->cols);
1466 if (scroll_amt < field->start_char)
1467 scroll_amt = 1;
1468 else
1469 scroll_amt -= field->start_char;
1471 scroll_amt = min(scroll_amt, amt);
1474 field->start_char += scroll_amt;
1475 field->cursor_xpos =
1476 _formi_tab_expanded_length(row->string,
1477 field->start_char,
1478 field->row_xpos
1479 + field->start_char) - 1;
1484 * Scroll the field backward the given number of characters.
1486 void
1487 _formi_hscroll_back(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt)
1489 field->start_char -= min(field->start_char, amt);
1490 field->cursor_xpos =
1491 _formi_tab_expanded_length(row->string, field->start_char,
1492 field->row_xpos
1493 + field->start_char) - 1;
1494 if (field->cursor_xpos >= field->cols) {
1495 field->row_xpos = 0;
1496 field->cursor_xpos = 0;
1501 * Find the different pages in the form fields and assign the form
1502 * page_starts array with the information to find them.
1505 _formi_find_pages(FORM *form)
1507 int i, cur_page = 0;
1509 if ((form->page_starts = (_FORMI_PAGE_START *)
1510 malloc((form->max_page + 1) * sizeof(_FORMI_PAGE_START))) == NULL)
1511 return E_SYSTEM_ERROR;
1513 /* initialise the page starts array */
1514 memset(form->page_starts, 0,
1515 (form->max_page + 1) * sizeof(_FORMI_PAGE_START));
1517 for (i =0; i < form->field_count; i++) {
1518 if (form->fields[i]->page_break == 1)
1519 cur_page++;
1520 if (form->page_starts[cur_page].in_use == 0) {
1521 form->page_starts[cur_page].in_use = 1;
1522 form->page_starts[cur_page].first = i;
1523 form->page_starts[cur_page].last = i;
1524 form->page_starts[cur_page].top_left = i;
1525 form->page_starts[cur_page].bottom_right = i;
1526 } else {
1527 form->page_starts[cur_page].last = i;
1528 form->page_starts[cur_page].top_left =
1529 _formi_top_left(form,
1530 form->page_starts[cur_page].top_left,
1532 form->page_starts[cur_page].bottom_right =
1533 _formi_bottom_right(form,
1534 form->page_starts[cur_page].bottom_right,
1539 return E_OK;
1543 * Completely redraw the field of the given form.
1545 void
1546 _formi_redraw_field(FORM *form, int field)
1548 unsigned int pre, post, flen, slen, i, j, start, line;
1549 unsigned int tab, cpos, len;
1550 char *str, c;
1551 FIELD *cur;
1552 _FORMI_FIELD_LINES *row;
1553 #ifdef DEBUG
1554 char buffer[100];
1555 #endif
1557 cur = form->fields[field];
1558 flen = cur->cols;
1559 slen = 0;
1560 start = 0;
1561 line = 0;
1563 for (row = cur->start_line; ((row != NULL) && (line < cur->rows));
1564 row = row->next, line++) {
1565 wmove(form->scrwin, (int) (cur->form_row + line),
1566 (int) cur->form_col);
1567 if ((cur->rows + cur->nrows) == 1) {
1568 if ((cur->cols + cur->start_char) >= row->length)
1569 len = row->length;
1570 else
1571 len = cur->cols + cur->start_char;
1572 if (row->string != NULL)
1573 slen = _formi_tab_expanded_length(
1574 row->string, cur->start_char, len);
1575 else
1576 slen = 0;
1578 if (slen > cur->cols)
1579 slen = cur->cols;
1580 slen += cur->start_char;
1581 } else
1582 slen = row->expanded;
1584 if ((cur->opts & O_STATIC) == O_STATIC) {
1585 switch (cur->justification) {
1586 case JUSTIFY_RIGHT:
1587 post = 0;
1588 if (flen < slen)
1589 pre = 0;
1590 else
1591 pre = flen - slen;
1592 break;
1594 case JUSTIFY_CENTER:
1595 if (flen < slen) {
1596 pre = 0;
1597 post = 0;
1598 } else {
1599 pre = flen - slen;
1600 post = pre = pre / 2;
1601 /* get padding right if
1602 centring is not even */
1603 if ((post + pre + slen) < flen)
1604 post++;
1606 break;
1608 case NO_JUSTIFICATION:
1609 case JUSTIFY_LEFT:
1610 default:
1611 pre = 0;
1612 if (flen <= slen)
1613 post = 0;
1614 else {
1615 post = flen - slen;
1616 if (post > flen)
1617 post = flen;
1619 break;
1621 } else {
1622 /* dynamic fields are not justified */
1623 pre = 0;
1624 if (flen <= slen)
1625 post = 0;
1626 else {
1627 post = flen - slen;
1628 if (post > flen)
1629 post = flen;
1632 /* but they do scroll.... */
1634 if (pre > cur->start_char - start)
1635 pre = pre - cur->start_char + start;
1636 else
1637 pre = 0;
1639 if (slen > cur->start_char) {
1640 slen -= cur->start_char;
1641 if (slen > flen)
1642 post = 0;
1643 else
1644 post = flen - slen;
1646 if (post > flen)
1647 post = flen;
1648 } else {
1649 slen = 0;
1650 post = flen - pre;
1654 if (form->cur_field == field)
1655 wattrset(form->scrwin, cur->fore);
1656 else
1657 wattrset(form->scrwin, cur->back);
1659 str = &row->string[cur->start_char];
1661 #ifdef DEBUG
1662 if (_formi_create_dbg_file() == E_OK) {
1663 fprintf(dbg,
1664 "redraw_field: start=%d, pre=%d, slen=%d, flen=%d, post=%d, start_char=%d\n",
1665 start, pre, slen, flen, post, cur->start_char);
1666 if (str != NULL) {
1667 if (row->expanded != 0) {
1668 strncpy(buffer, str, flen);
1669 } else {
1670 strcpy(buffer, "(empty)");
1672 } else {
1673 strcpy(buffer, "(null)");
1675 buffer[flen] = '\0';
1676 fprintf(dbg, "redraw_field: %s\n", buffer);
1678 #endif
1680 for (i = start + cur->start_char; i < pre; i++)
1681 waddch(form->scrwin, cur->pad);
1683 #ifdef DEBUG
1684 fprintf(dbg, "redraw_field: will add %d chars\n",
1685 min(slen, flen));
1686 #endif
1687 for (i = 0, cpos = cur->start_char; i < min(slen, flen);
1688 i++, str++, cpos++)
1690 c = *str;
1691 tab = 0; /* just to shut gcc up */
1692 #ifdef DEBUG
1693 fprintf(dbg, "adding char str[%d]=%c\n",
1694 cpos + cur->start_char, c);
1695 #endif
1696 if (((cur->opts & O_PUBLIC) != O_PUBLIC)) {
1697 if (c == '\t')
1698 tab = add_tab(form, row, cpos,
1699 cur->pad);
1700 else
1701 waddch(form->scrwin, cur->pad);
1702 } else if ((cur->opts & O_VISIBLE) == O_VISIBLE) {
1703 if (c == '\t')
1704 tab = add_tab(form, row, cpos, ' ');
1705 else
1706 waddch(form->scrwin, c);
1707 } else {
1708 if (c == '\t')
1709 tab = add_tab(form, row, cpos, ' ');
1710 else
1711 waddch(form->scrwin, ' ');
1715 * If we have had a tab then skip forward
1716 * the requisite number of chars to keep
1717 * things in sync.
1719 if (c == '\t')
1720 i += tab - 1;
1723 for (i = 0; i < post; i++)
1724 waddch(form->scrwin, cur->pad);
1727 for (i = line; i < cur->rows; i++) {
1728 wmove(form->scrwin, (int) (cur->form_row + i),
1729 (int) cur->form_col);
1731 if (form->cur_field == field)
1732 wattrset(form->scrwin, cur->fore);
1733 else
1734 wattrset(form->scrwin, cur->back);
1736 for (j = 0; j < cur->cols; j++) {
1737 waddch(form->scrwin, cur->pad);
1741 wattrset(form->scrwin, cur->back);
1742 return;
1746 * Add the correct number of the given character to simulate a tab
1747 * in the field.
1749 static int
1750 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c)
1752 int j;
1753 _formi_tab_t *ts = row->tabs;
1755 while ((ts != NULL) && (ts->pos != i))
1756 ts = ts->fwd;
1758 assert(ts != NULL);
1760 for (j = 0; j < ts->size; j++)
1761 waddch(form->scrwin, c);
1763 return ts->size;
1768 * Display the fields attached to the form that are on the current page
1769 * on the screen.
1773 _formi_draw_page(FORM *form)
1775 int i;
1777 if (form->page_starts[form->page].in_use == 0)
1778 return E_BAD_ARGUMENT;
1780 wclear(form->scrwin);
1782 for (i = form->page_starts[form->page].first;
1783 i <= form->page_starts[form->page].last; i++)
1784 _formi_redraw_field(form, i);
1786 return E_OK;
1790 * Add the character c at the position pos in buffer 0 of the given field
1793 _formi_add_char(FIELD *field, unsigned int pos, char c)
1795 char *new, old_c;
1796 unsigned int new_size;
1797 int status;
1798 _FORMI_FIELD_LINES *row, *temp, *next_temp;
1800 row = field->cur_line;
1803 * If buffer has not had a string before, set it to a blank
1804 * string. Everything should flow from there....
1806 if (row->string == NULL) {
1807 if ((row->string = (char *) malloc((size_t)INITIAL_LINE_ALLOC))
1808 == NULL)
1809 return E_SYSTEM_ERROR;
1810 row->string[0] = '\0';
1811 row->allocated = INITIAL_LINE_ALLOC;
1812 row->length = 0;
1813 row->expanded = 0;
1816 if (_formi_validate_char(field, c) != E_OK) {
1817 #ifdef DEBUG
1818 fprintf(dbg, "add_char: char %c failed char validation\n", c);
1819 #endif
1820 return E_INVALID_FIELD;
1823 if ((c == '\t') && (field->cols <= 8)) {
1824 #ifdef DEBUG
1825 fprintf(dbg, "add_char: field too small for a tab\n");
1826 #endif
1827 return E_NO_ROOM;
1830 #ifdef DEBUG
1831 fprintf(dbg, "add_char: pos=%d, char=%c\n", pos, c);
1832 fprintf(dbg, "add_char enter: xpos=%d, row_pos=%d, start=%d\n",
1833 field->cursor_xpos, field->row_xpos, field->start_char);
1834 fprintf(dbg, "add_char enter: length=%d(%d), allocated=%d\n",
1835 row->expanded, row->length, row->allocated);
1836 fprintf(dbg, "add_char enter: %s\n", row->string);
1837 fprintf(dbg, "add_char enter: buf0_status=%d\n", field->buf0_status);
1838 #endif
1839 if (((field->opts & O_BLANK) == O_BLANK) &&
1840 (field->buf0_status == FALSE) &&
1841 ((field->row_xpos + field->start_char) == 0)) {
1842 row = field->lines;
1843 if (row->next != NULL) {
1844 /* shift all but one line structs to free list */
1845 temp = row->next;
1846 do {
1847 next_temp = temp->next;
1848 add_to_free(field, temp);
1849 temp = next_temp;
1850 } while (temp != NULL);
1853 row->length = 0;
1854 row->string[0] = '\0';
1855 pos = 0;
1856 field->start_char = 0;
1857 field->start_line = row;
1858 field->cur_line = row;
1859 field->row_count = 1;
1860 field->row_xpos = 0;
1861 field->cursor_ypos = 0;
1862 row->expanded = 0;
1863 row->length = 0;
1864 _formi_init_field_xpos(field);
1868 if ((field->overlay == 0)
1869 || ((field->overlay == 1) && (pos >= row->length))) {
1870 /* first check if the field can have more chars...*/
1871 if (check_field_size(field) == FALSE)
1872 return E_REQUEST_DENIED;
1874 if (row->length + 2
1875 >= row->allocated) {
1876 new_size = row->allocated + 16 - (row->allocated % 16);
1877 if ((new = (char *) realloc(row->string,
1878 (size_t) new_size )) == NULL)
1879 return E_SYSTEM_ERROR;
1880 row->allocated = new_size;
1881 row->string = new;
1885 if ((field->overlay == 0) && (row->length > pos)) {
1886 bcopy(&row->string[pos], &row->string[pos + 1],
1887 (size_t) (row->length - pos + 1));
1890 old_c = row->string[pos];
1891 row->string[pos] = c;
1892 if (pos >= row->length) {
1893 /* make sure the string is terminated if we are at the
1894 * end of the string, the terminator would be missing
1895 * if we are are at the end of the field.
1897 row->string[pos + 1] = '\0';
1900 /* only increment the length if we are inserting characters
1901 * OR if we are at the end of the field in overlay mode.
1903 if ((field->overlay == 0)
1904 || ((field->overlay == 1) && (pos >= row->length))) {
1905 row->length++;
1908 _formi_calculate_tabs(row);
1909 row->expanded = _formi_tab_expanded_length(row->string, 0,
1910 row->length - 1);
1912 /* wrap the field, if needed */
1913 status = _formi_wrap_field(field, row);
1915 row = field->cur_line;
1916 pos = field->row_xpos;
1919 * check the wrap worked or that we have not exceeded the
1920 * max field size - this can happen if the field is re-wrapped
1921 * and the row count is increased past the set limit.
1923 if ((status != E_OK) || (check_field_size(field) == FALSE)) {
1924 if ((field->overlay == 0)
1925 || ((field->overlay == 1)
1926 && (pos >= (row->length - 1) /*XXXX- append check???*/))) {
1928 * wrap failed for some reason, back out the
1929 * char insert
1931 bcopy(&row->string[pos + 1], &row->string[pos],
1932 (size_t) (row->length - pos));
1933 row->length--;
1934 if (pos > 0)
1935 pos--;
1936 } else if (field->overlay == 1) {
1937 /* back out character overlay */
1938 row->string[pos] = old_c;
1941 _formi_calculate_tabs(row);
1943 _formi_wrap_field(field, row);
1945 * If we are here then either the status is bad or we
1946 * simply ran out of room. If the status is E_OK then
1947 * we ran out of room, let the form driver know this.
1949 if (status == E_OK)
1950 status = E_REQUEST_DENIED;
1952 } else {
1953 field->buf0_status = TRUE;
1954 field->row_xpos++;
1955 if ((field->rows + field->nrows) == 1) {
1956 status = _formi_set_cursor_xpos(field, FALSE);
1957 } else {
1958 field->cursor_xpos =
1959 _formi_tab_expanded_length(
1960 row->string, 0, field->row_xpos - 1);
1963 * Annoying corner case - if we are right in
1964 * the bottom right corner of the field we
1965 * need to scroll the field one line so the
1966 * cursor is positioned correctly in the
1967 * field.
1969 if ((field->cursor_xpos >= field->cols) &&
1970 (field->cursor_ypos == (field->rows - 1))) {
1971 field->cursor_ypos--;
1972 field->start_line = field->start_line->next;
1977 assert((field->cursor_xpos <= field->cols)
1978 && (field->cursor_ypos < 400000));
1980 #ifdef DEBUG
1981 fprintf(dbg, "add_char exit: xpos=%d, row_pos=%d, start=%d\n",
1982 field->cursor_xpos, field->row_xpos, field->start_char);
1983 fprintf(dbg, "add_char_exit: length=%d(%d), allocated=%d\n",
1984 row->expanded, row->length, row->allocated);
1985 fprintf(dbg, "add_char exit: ypos=%d, start_line=%p\n",
1986 field->cursor_ypos, field->start_line);
1987 fprintf(dbg,"add_char exit: %s\n", row->string);
1988 fprintf(dbg, "add_char exit: buf0_status=%d\n", field->buf0_status);
1989 fprintf(dbg, "add_char exit: status = %s\n",
1990 (status == E_OK)? "OK" : "FAILED");
1991 #endif
1992 return status;
1996 * Set the position of the cursor on the screen in the row depending on
1997 * where the current position in the string is and the justification
1998 * that is to be applied to the field. Justification is only applied
1999 * to single row, static fields.
2001 static int
2002 _formi_set_cursor_xpos(FIELD *field, int noscroll)
2004 int just, pos;
2006 just = field->justification;
2007 pos = field->start_char + field->row_xpos;
2009 #ifdef DEBUG
2010 fprintf(dbg,
2011 "cursor_xpos enter: pos %d, start_char %d, row_xpos %d, xpos %d\n",
2012 pos, field->start_char, field->row_xpos, field->cursor_xpos);
2013 #endif
2016 * make sure we apply the correct justification to non-static
2017 * fields.
2019 if (((field->rows + field->nrows) != 1) ||
2020 ((field->opts & O_STATIC) != O_STATIC))
2021 just = JUSTIFY_LEFT;
2023 switch (just) {
2024 case JUSTIFY_RIGHT:
2025 field->cursor_xpos = field->cols - 1
2026 - _formi_tab_expanded_length(
2027 field->cur_line->string, 0,
2028 field->cur_line->length - 1)
2029 + _formi_tab_expanded_length(
2030 field->cur_line->string, 0,
2031 field->row_xpos);
2032 break;
2034 case JUSTIFY_CENTER:
2035 field->cursor_xpos = ((field->cols - 1)
2036 - _formi_tab_expanded_length(
2037 field->cur_line->string, 0,
2038 field->cur_line->length - 1) + 1) / 2
2039 + _formi_tab_expanded_length(field->cur_line->string,
2040 0, field->row_xpos);
2042 if (field->cursor_xpos > (field->cols - 1))
2043 field->cursor_xpos = (field->cols - 1);
2044 break;
2046 default:
2047 field->cursor_xpos = _formi_tab_expanded_length(
2048 field->cur_line->string,
2049 field->start_char,
2050 field->row_xpos + field->start_char);
2051 if ((field->cursor_xpos <= (field->cols - 1)) &&
2052 ((field->start_char + field->row_xpos)
2053 < field->cur_line->length))
2054 field->cursor_xpos--;
2056 if (field->cursor_xpos > (field->cols - 1)) {
2057 if ((field->opts & O_STATIC) == O_STATIC) {
2058 field->start_char = 0;
2060 if (field->row_xpos
2061 == (field->cur_line->length - 1)) {
2062 field->cursor_xpos = field->cols - 1;
2063 } else {
2064 field->cursor_xpos =
2065 _formi_tab_expanded_length(
2066 field->cur_line->string,
2067 field->start_char,
2068 field->row_xpos
2069 + field->start_char
2070 - 1) - 1;
2072 } else {
2073 if (noscroll == FALSE) {
2074 field->start_char =
2075 tab_fit_window(
2076 field,
2077 field->start_char
2078 + field->row_xpos,
2079 field->cols);
2080 field->row_xpos = pos
2081 - field->start_char;
2082 field->cursor_xpos =
2083 _formi_tab_expanded_length(
2084 field->cur_line->string,
2085 field->start_char,
2086 field->row_xpos
2087 + field->start_char - 1);
2088 } else {
2089 field->cursor_xpos = (field->cols - 1);
2094 break;
2097 #ifdef DEBUG
2098 fprintf(dbg,
2099 "cursor_xpos exit: pos %d, start_char %d, row_xpos %d, xpos %d\n",
2100 pos, field->start_char, field->row_xpos, field->cursor_xpos);
2101 #endif
2102 return E_OK;
2106 * Manipulate the text in a field, this takes the given form and performs
2107 * the passed driver command on the current text field. Returns 1 if the
2108 * text field was modified.
2111 _formi_manipulate_field(FORM *form, int c)
2113 FIELD *cur;
2114 char *str, saved;
2115 unsigned int start, end, pos, status, old_count, size;
2116 unsigned int old_xpos, old_row_pos;
2117 int len, wb;
2118 bool eat_char;
2119 _FORMI_FIELD_LINES *row, *rs;
2121 cur = form->fields[form->cur_field];
2122 if (cur->cur_line->string == NULL)
2123 return E_REQUEST_DENIED;
2125 #ifdef DEBUG
2126 fprintf(dbg, "entry: request is REQ_%s\n", reqs[c - REQ_MIN_REQUEST]);
2127 fprintf(dbg,
2128 "entry: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
2129 cur->cursor_xpos, cur->row_xpos, cur->start_char,
2130 cur->cur_line->length, cur->cur_line->allocated);
2131 fprintf(dbg, "entry: start_line=%p, ypos=%d\n", cur->start_line,
2132 cur->cursor_ypos);
2133 fprintf(dbg, "entry: string=");
2134 if (cur->cur_line->string == NULL)
2135 fprintf(dbg, "(null)\n");
2136 else
2137 fprintf(dbg, "\"%s\"\n", cur->cur_line->string);
2138 #endif
2140 /* Cannot manipulate a null string! */
2141 if (cur->cur_line->string == NULL)
2142 return E_REQUEST_DENIED;
2144 saved = '\0';
2145 row = cur->cur_line;
2147 switch (c) {
2148 case REQ_RIGHT_CHAR:
2150 * The right_char request performs the same function
2151 * as the next_char request except that the cursor is
2152 * not wrapped if it is at the end of the line, so
2153 * check if the cursor is at the end of the line and
2154 * deny the request otherwise just fall through to
2155 * the next_char request handler.
2157 if (cur->cursor_xpos >= cur->cols - 1)
2158 return E_REQUEST_DENIED;
2160 /* FALLTHRU */
2162 case REQ_NEXT_CHAR:
2163 /* for a dynamic field allow an offset of one more
2164 * char so we can insert chars after end of string.
2165 * Static fields cannot do this so deny request if
2166 * cursor is at the end of the field.
2168 if (((cur->opts & O_STATIC) == O_STATIC) &&
2169 (cur->row_xpos == cur->cols - 1) &&
2170 ((cur->rows + cur->nrows) == 1))
2171 return E_REQUEST_DENIED;
2173 if (((cur->rows + cur->nrows) == 1) &&
2174 (cur->row_xpos + cur->start_char + 1) > row->length)
2175 return E_REQUEST_DENIED;
2177 if ((cur->rows + cur->nrows) == 1) {
2178 cur->row_xpos++;
2179 _formi_set_cursor_xpos(cur, (c == REQ_RIGHT_CHAR));
2180 } else {
2181 if (cur->cursor_xpos >= (row->expanded - 1)) {
2182 if ((row->next == NULL) ||
2183 (c == REQ_RIGHT_CHAR))
2184 return E_REQUEST_DENIED;
2186 cur->cursor_xpos = 0;
2187 cur->row_xpos = 0;
2188 cur->cur_line = cur->cur_line->next;
2189 if (cur->cursor_ypos == (cur->rows - 1))
2190 cur->start_line =
2191 cur->start_line->next;
2192 else
2193 cur->cursor_ypos++;
2194 } else {
2195 old_xpos = cur->cursor_xpos;
2196 old_row_pos = cur->row_xpos;
2197 if (row->string[cur->row_xpos] == '\t')
2198 cur->cursor_xpos += tab_size(row,
2199 cur->row_xpos);
2200 else
2201 cur->cursor_xpos++;
2202 cur->row_xpos++;
2203 if (cur->cursor_xpos
2204 >= row->expanded) {
2205 if ((row->next == NULL) ||
2206 (c == REQ_RIGHT_CHAR)) {
2207 cur->cursor_xpos = old_xpos;
2208 cur->row_xpos = old_row_pos;
2209 return E_REQUEST_DENIED;
2212 cur->cursor_xpos = 0;
2213 cur->row_xpos = 0;
2214 cur->cur_line = cur->cur_line->next;
2215 if (cur->cursor_ypos
2216 == (cur->rows - 1))
2217 cur->start_line =
2218 cur->start_line->next;
2219 else
2220 cur->cursor_ypos++;
2225 break;
2227 case REQ_LEFT_CHAR:
2229 * The behaviour of left_char is the same as prev_char
2230 * except that the cursor will not wrap if it has
2231 * reached the LHS of the field, so just check this
2232 * and fall through if we are not at the LHS.
2234 if (cur->cursor_xpos == 0)
2235 return E_REQUEST_DENIED;
2237 /* FALLTHRU */
2238 case REQ_PREV_CHAR:
2239 if ((cur->rows + cur->nrows) == 1) {
2240 if (cur->row_xpos == 0) {
2241 if (cur->start_char > 0)
2242 cur->start_char--;
2243 else
2244 return E_REQUEST_DENIED;
2245 } else {
2246 cur->row_xpos--;
2247 _formi_set_cursor_xpos(cur, FALSE);
2249 } else {
2250 if ((cur->cursor_xpos == 0) &&
2251 (cur->cursor_ypos == 0) &&
2252 (cur->start_line->prev == NULL))
2253 return E_REQUEST_DENIED;
2255 pos = cur->row_xpos;
2256 if (cur->cursor_xpos > 0) {
2257 if (row->string[pos] == '\t') {
2258 size = tab_size(row, pos);
2259 if (size > cur->cursor_xpos) {
2260 cur->cursor_xpos = 0;
2261 cur->row_xpos = 0;
2262 } else {
2263 cur->row_xpos--;
2264 cur->cursor_xpos -= size;
2266 } else {
2267 cur->cursor_xpos--;
2268 cur->row_xpos--;
2270 } else {
2271 cur->cur_line = cur->cur_line->prev;
2272 if (cur->cursor_ypos > 0)
2273 cur->cursor_ypos--;
2274 else
2275 cur->start_line =
2276 cur->start_line->prev;
2277 row = cur->cur_line;
2278 if (row->expanded > 0) {
2279 cur->cursor_xpos = row->expanded - 1;
2280 } else {
2281 cur->cursor_xpos = 0;
2284 if (row->length > 0)
2285 cur->row_xpos = row->length - 1;
2286 else
2287 cur->row_xpos = 0;
2291 break;
2293 case REQ_DOWN_CHAR:
2295 * The down_char request has the same functionality as
2296 * the next_line request excepting that the field is not
2297 * scrolled if the cursor is at the bottom of the field.
2298 * Check to see if the cursor is at the bottom of the field
2299 * and if it is then deny the request otherwise fall
2300 * through to the next_line handler.
2302 if (cur->cursor_ypos >= cur->rows - 1)
2303 return E_REQUEST_DENIED;
2305 /* FALLTHRU */
2307 case REQ_NEXT_LINE:
2308 if ((row->next == NULL) || (cur->cur_line->next == NULL))
2309 return E_REQUEST_DENIED;
2311 cur->cur_line = cur->cur_line->next;
2312 if ((cur->cursor_ypos + 1) >= cur->rows) {
2313 cur->start_line = cur->start_line->next;
2314 } else
2315 cur->cursor_ypos++;
2316 row = cur->cur_line;
2318 if (row->length == 0) {
2319 cur->row_xpos = 0;
2320 cur->cursor_xpos = 0;
2321 } else {
2322 if (cur->cursor_xpos > (row->expanded - 1))
2323 cur->cursor_xpos = row->expanded - 1;
2325 cur->row_xpos = tab_fit_len(row, cur->cursor_xpos + 1);
2326 if (cur->row_xpos == 0)
2327 cur->cursor_xpos = 0;
2328 else
2329 cur->cursor_xpos =
2330 _formi_tab_expanded_length(
2331 row->string, 0, cur->row_xpos);
2332 if (cur->cursor_xpos > 0)
2333 cur->cursor_xpos--;
2335 break;
2337 case REQ_UP_CHAR:
2339 * The up_char request has the same functionality as
2340 * the prev_line request excepting the field is not
2341 * scrolled, check if the cursor is at the top of the
2342 * field, if it is deny the request otherwise fall
2343 * through to the prev_line handler.
2345 if (cur->cursor_ypos == 0)
2346 return E_REQUEST_DENIED;
2348 /* FALLTHRU */
2350 case REQ_PREV_LINE:
2351 if (cur->cur_line->prev == NULL)
2352 return E_REQUEST_DENIED;
2354 if (cur->cursor_ypos == 0) {
2355 if (cur->start_line->prev == NULL)
2356 return E_REQUEST_DENIED;
2357 cur->start_line = cur->start_line->prev;
2358 } else
2359 cur->cursor_ypos--;
2361 cur->cur_line = cur->cur_line->prev;
2362 row = cur->cur_line;
2364 if (row->length == 0) {
2365 cur->row_xpos = 0;
2366 cur->cursor_xpos = 0;
2367 } else {
2368 if (cur->cursor_xpos > (row->expanded - 1))
2369 cur->cursor_xpos = row->expanded - 1;
2371 cur->row_xpos = tab_fit_len(row, cur->cursor_xpos + 1);
2372 cur->cursor_xpos =
2373 _formi_tab_expanded_length(row->string,
2374 0, cur->row_xpos);
2375 if (cur->cursor_xpos > 0)
2376 cur->cursor_xpos--;
2378 break;
2380 case REQ_NEXT_WORD:
2381 start = cur->row_xpos + cur->start_char;
2382 str = row->string;
2384 wb = find_eow(cur, start, FALSE, &row);
2385 if (wb < 0)
2386 return wb;
2388 start = wb;
2389 /* check if we hit the end */
2390 if (str[start] == '\0')
2391 return E_REQUEST_DENIED;
2393 /* otherwise we must have found the start of a word...*/
2394 if ((cur->rows + cur->nrows) == 1) {
2395 /* single line field */
2396 size = _formi_tab_expanded_length(str,
2397 cur->start_char, start);
2398 if (size < cur->cols) {
2399 cur->row_xpos = start - cur->start_char;
2400 } else {
2401 cur->start_char = start;
2402 cur->row_xpos = 0;
2404 _formi_set_cursor_xpos(cur, FALSE);
2405 } else {
2406 /* multiline field */
2407 cur->cur_line = row;
2408 adjust_ypos(cur, row);
2410 cur->row_xpos = start;
2411 cur->cursor_xpos =
2412 _formi_tab_expanded_length(
2413 row->string, 0, cur->row_xpos) - 1;
2415 break;
2417 case REQ_PREV_WORD:
2418 start = cur->start_char + cur->row_xpos;
2419 if (cur->start_char > 0)
2420 start--;
2422 if ((start == 0) && (row->prev == NULL))
2423 return E_REQUEST_DENIED;
2425 if (start == 0) {
2426 row = row->prev;
2427 if (row->length > 0)
2428 start = row->length - 1;
2429 else
2430 start = 0;
2433 str = row->string;
2435 start = find_sow(start, &row);
2437 if ((cur->rows + cur->nrows) == 1) {
2438 /* single line field */
2439 size = _formi_tab_expanded_length(str,
2440 cur->start_char, start);
2442 if (start > cur->start_char) {
2443 cur->row_xpos = start - cur->start_char;
2444 } else {
2445 cur->start_char = start;
2446 cur->row_xpos = 0;
2448 _formi_set_cursor_xpos(cur, FALSE);
2449 } else {
2450 /* multiline field */
2451 cur->cur_line = row;
2452 adjust_ypos(cur, row);
2453 cur->row_xpos = start;
2454 cur->cursor_xpos =
2455 _formi_tab_expanded_length(
2456 row->string, 0,
2457 cur->row_xpos) - 1;
2460 break;
2462 case REQ_BEG_FIELD:
2463 cur->start_char = 0;
2464 while (cur->start_line->prev != NULL)
2465 cur->start_line = cur->start_line->prev;
2466 cur->cur_line = cur->start_line;
2467 cur->row_xpos = 0;
2468 _formi_init_field_xpos(cur);
2469 cur->cursor_ypos = 0;
2470 break;
2472 case REQ_BEG_LINE:
2473 cur->row_xpos = 0;
2474 _formi_init_field_xpos(cur);
2475 cur->start_char = 0;
2476 break;
2478 case REQ_END_FIELD:
2479 while (cur->cur_line->next != NULL)
2480 cur->cur_line = cur->cur_line->next;
2482 if (cur->row_count > cur->rows) {
2483 cur->start_line = cur->cur_line;
2484 pos = cur->rows - 1;
2485 while (pos > 0) {
2486 cur->start_line = cur->start_line->prev;
2487 pos--;
2489 cur->cursor_ypos = cur->rows - 1;
2490 } else {
2491 cur->cursor_ypos = cur->row_count - 1;
2494 /* we fall through here deliberately, we are on the
2495 * correct row, now we need to get to the end of the
2496 * line.
2498 /* FALLTHRU */
2500 case REQ_END_LINE:
2501 row = cur->cur_line;
2503 if ((cur->rows + cur->nrows) == 1) {
2504 if (row->expanded > cur->cols - 1) {
2505 if ((cur->opts & O_STATIC) != O_STATIC) {
2506 cur->start_char = tab_fit_window(
2507 cur, row->length,
2508 cur->cols) + 1;
2509 cur->row_xpos = row->length
2510 - cur->start_char;
2511 } else {
2512 cur->start_char = 0;
2513 cur->row_xpos = cur->cols - 1;
2515 } else {
2516 cur->row_xpos = row->length + 1;
2517 cur->start_char = 0;
2519 _formi_set_cursor_xpos(cur, FALSE);
2520 } else {
2521 cur->row_xpos = row->length - 1;
2522 cur->cursor_xpos = row->expanded - 1;
2523 if (row->next == NULL) {
2524 cur->row_xpos++;
2525 cur->cursor_xpos++;
2528 break;
2530 case REQ_NEW_LINE:
2531 start = cur->start_char + cur->row_xpos;
2532 if ((status = split_line(cur, TRUE, start, &row)) != E_OK)
2533 return status;
2534 cur->cur_line->hard_ret = TRUE;
2535 cur->cursor_xpos = 0;
2536 cur->row_xpos = 0;
2537 break;
2539 case REQ_INS_CHAR:
2540 if ((status = _formi_add_char(cur, cur->start_char
2541 + cur->row_xpos,
2542 cur->pad)) != E_OK)
2543 return status;
2544 break;
2546 case REQ_INS_LINE:
2547 if ((status = split_line(cur, TRUE, 0, &row)) != E_OK)
2548 return status;
2549 cur->cur_line->hard_ret = TRUE;
2550 break;
2552 case REQ_DEL_CHAR:
2553 row = cur->cur_line;
2554 start = cur->start_char + cur->row_xpos;
2555 end = row->length - 1;
2556 if ((start >= row->length) && (row->next == NULL))
2557 return E_REQUEST_DENIED;
2559 if ((start == row->length - 1) || (row->length == 0)) {
2560 if ((cur->rows + cur->nrows) > 1) {
2562 * Firstly, check if the current line has
2563 * a hard return. In this case we just
2564 * want to "delete" the hard return and
2565 * re-wrap the field. The hard return
2566 * does not occupy a character space in
2567 * the buffer but we must make it appear
2568 * like it does for a deletion.
2570 if (row->hard_ret == TRUE) {
2571 row->hard_ret = FALSE;
2572 if (_formi_join_line(cur, &row,
2573 JOIN_NEXT)
2574 != E_OK) {
2575 row->hard_ret = TRUE;
2576 return 0;
2577 } else {
2578 return 1;
2583 * If we have more than one row, join the
2584 * next row to make things easier unless
2585 * we are at the end of the string, in
2586 * that case the join would fail but we
2587 * really want to delete the last char
2588 * in the field.
2590 if (row->next != NULL) {
2591 if (_formi_join_line(cur, &row,
2592 JOIN_NEXT_NW)
2593 != E_OK) {
2594 return E_REQUEST_DENIED;
2600 saved = row->string[start];
2601 bcopy(&row->string[start + 1], &row->string[start],
2602 (size_t) (end - start + 1));
2603 row->string[end] = '\0';
2604 row->length--;
2605 if (row->length > 0)
2606 row->expanded = _formi_tab_expanded_length(
2607 row->string, 0, row->length - 1);
2608 else
2609 row->expanded = 0;
2612 * recalculate tabs for a single line field, multiline
2613 * fields will do this when the field is wrapped.
2615 if ((cur->rows + cur->nrows) == 1)
2616 _formi_calculate_tabs(row);
2618 * if we are at the end of the string then back the
2619 * cursor pos up one to stick on the end of the line
2621 if (start == row->length) {
2622 if (row->length > 1) {
2623 if ((cur->rows + cur->nrows) == 1) {
2624 pos = cur->row_xpos + cur->start_char;
2625 cur->start_char =
2626 tab_fit_window(
2627 cur,
2628 cur->start_char + cur->row_xpos,
2629 cur->cols);
2630 cur->row_xpos = pos - cur->start_char
2631 - 1;
2632 _formi_set_cursor_xpos(cur, FALSE);
2633 } else {
2634 if (cur->row_xpos == 0) {
2635 if (row->next != NULL) {
2636 if (_formi_join_line(
2637 cur, &row,
2638 JOIN_PREV_NW)
2639 != E_OK) {
2640 return E_REQUEST_DENIED;
2642 } else {
2643 if (cur->row_count > 1)
2644 cur->row_count--;
2649 cur->row_xpos = start - 1;
2650 cur->cursor_xpos =
2651 _formi_tab_expanded_length(
2652 row->string,
2653 0, cur->row_xpos - 1);
2654 if ((cur->cursor_xpos > 0)
2655 && (start != (row->expanded - 1)))
2656 cur->cursor_xpos--;
2659 start--;
2660 } else {
2661 start = 0;
2662 cur->row_xpos = 0;
2663 _formi_init_field_xpos(cur);
2667 if ((cur->rows + cur->nrows) > 1) {
2668 if (_formi_wrap_field(cur, row) != E_OK) {
2669 bcopy(&row->string[start],
2670 &row->string[start + 1],
2671 (size_t) (end - start));
2672 row->length++;
2673 row->string[start] = saved;
2674 _formi_wrap_field(cur, row);
2675 return E_REQUEST_DENIED;
2678 break;
2680 case REQ_DEL_PREV:
2681 if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
2682 && (cur->start_line->prev == NULL)
2683 && (cur->cursor_ypos == 0))
2684 return E_REQUEST_DENIED;
2686 row = cur->cur_line;
2687 start = cur->row_xpos + cur->start_char;
2688 end = row->length - 1;
2689 eat_char = TRUE;
2691 if ((cur->start_char + cur->row_xpos) == 0) {
2692 if (row->prev == NULL)
2693 return E_REQUEST_DENIED;
2696 * If we are a multiline field then check if
2697 * the line above has a hard return. If it does
2698 * then just "eat" the hard return and re-wrap
2699 * the field.
2701 if (row->prev->hard_ret == TRUE) {
2702 row->prev->hard_ret = FALSE;
2703 if (_formi_join_line(cur, &row,
2704 JOIN_PREV) != E_OK) {
2705 row->prev->hard_ret = TRUE;
2706 return 0;
2709 eat_char = FALSE;
2710 } else {
2711 start = row->prev->length;
2713 * Join this line to the previous
2714 * one.
2716 if (_formi_join_line(cur, &row,
2717 JOIN_PREV_NW) != E_OK) {
2718 return 0;
2720 end = row->length - 1;
2724 if (eat_char == TRUE) {
2726 * eat a char from the buffer. Normally we do
2727 * this unless we have deleted a "hard return"
2728 * in which case we just want to join the lines
2729 * without losing a char.
2731 saved = row->string[start - 1];
2732 bcopy(&row->string[start], &row->string[start - 1],
2733 (size_t) (end - start + 1));
2734 row->length--;
2735 row->string[row->length] = '\0';
2736 row->expanded = _formi_tab_expanded_length(
2737 row->string, 0, row->length - 1);
2740 if ((cur->rows + cur->nrows) == 1) {
2741 _formi_calculate_tabs(row);
2742 pos = cur->row_xpos + cur->start_char;
2743 if (pos > 0)
2744 pos--;
2745 cur->start_char =
2746 tab_fit_window(cur,
2747 cur->start_char + cur->row_xpos,
2748 cur->cols);
2749 cur->row_xpos = pos - cur->start_char;
2750 _formi_set_cursor_xpos(cur, FALSE);
2751 } else {
2752 if (eat_char == TRUE) {
2753 cur->row_xpos--;
2754 if (cur->row_xpos > 0)
2755 cur->cursor_xpos =
2756 _formi_tab_expanded_length(
2757 row->string, 0,
2758 cur->row_xpos - 1);
2759 else
2760 cur->cursor_xpos = 0;
2763 if ((_formi_wrap_field(cur, row) != E_OK)) {
2764 bcopy(&row->string[start - 1],
2765 &row->string[start],
2766 (size_t) (end - start));
2767 row->length++;
2768 row->string[start - 1] = saved;
2769 row->string[row->length] = '\0';
2770 _formi_wrap_field(cur, row);
2771 return E_REQUEST_DENIED;
2774 break;
2776 case REQ_DEL_LINE:
2777 if (((cur->rows + cur->nrows) == 1) ||
2778 (cur->row_count == 1)) {
2779 /* single line case */
2780 row->length = 0;
2781 row->expanded = row->length = 0;
2782 cur->row_xpos = 0;
2783 _formi_init_field_xpos(cur);
2784 cur->cursor_ypos = 0;
2785 } else {
2786 /* multiline field */
2787 old_count = cur->row_count;
2788 cur->row_count--;
2789 if (cur->row_count == 0)
2790 cur->row_count = 1;
2792 if (old_count == 1) {
2793 row->expanded = row->length = 0;
2794 cur->cursor_xpos = 0;
2795 cur->row_xpos = 0;
2796 cur->cursor_ypos = 0;
2797 } else
2798 add_to_free(cur, row);
2800 if (row->next == NULL) {
2801 if (cur->cursor_ypos == 0) {
2802 if (cur->start_line->prev != NULL) {
2803 cur->start_line =
2804 cur->start_line->prev;
2806 } else {
2807 cur->cursor_ypos--;
2811 if (old_count > 1) {
2812 if (cur->cursor_xpos > row->expanded) {
2813 cur->cursor_xpos = row->expanded - 1;
2814 cur->row_xpos = row->length - 1;
2817 cur->start_line = cur->lines;
2818 rs = cur->start_line;
2819 cur->cursor_ypos = 0;
2820 while (rs != row) {
2821 if (cur->cursor_ypos < cur->rows)
2822 cur->cursor_ypos++;
2823 else
2824 cur->start_line =
2825 cur->start_line->next;
2826 rs = rs->next;
2830 break;
2832 case REQ_DEL_WORD:
2833 start = cur->start_char + cur->row_xpos;
2834 str = row->string;
2836 wb = find_eow(cur, start, TRUE, &row);
2837 if (wb < 0)
2838 return wb;
2840 end = wb;
2843 * If not at the start of a word then find the start,
2844 * we cannot blindly call find_sow because this will
2845 * skip back a word if we are already at the start of
2846 * a word.
2848 if ((start > 0)
2849 && !(isblank(str[start - 1]) && !isblank(str[start])))
2850 start = find_sow(start, &row);
2851 str = row->string;
2852 /* XXXX hmmmm what if start and end on diff rows? XXXX */
2853 bcopy(&str[end], &str[start],
2854 (size_t) (row->length - end + 1));
2855 len = end - start;
2856 row->length -= len;
2858 if ((cur->rows + cur->nrows) > 1) {
2859 row = cur->start_line + cur->cursor_ypos;
2860 if (row->next != NULL) {
2862 * if not on the last row we need to
2863 * join on the next row so the line
2864 * will be re-wrapped.
2866 _formi_join_line(cur, &row, JOIN_NEXT_NW);
2868 _formi_wrap_field(cur, row);
2869 cur->row_xpos = start;
2870 cur->cursor_xpos = _formi_tab_expanded_length(
2871 row->string, 0, cur->row_xpos);
2872 if (cur->cursor_xpos > 0)
2873 cur->cursor_xpos--;
2874 } else {
2875 _formi_calculate_tabs(row);
2876 cur->row_xpos = start - cur->start_char;
2877 if (cur->row_xpos > 0)
2878 cur->row_xpos--;
2879 _formi_set_cursor_xpos(cur, FALSE);
2881 break;
2883 case REQ_CLR_EOL:
2884 row->string[cur->row_xpos + 1] = '\0';
2885 row->length = cur->row_xpos + 1;
2886 row->expanded = cur->cursor_xpos + 1;
2887 break;
2889 case REQ_CLR_EOF:
2890 row = cur->cur_line->next;
2891 while (row != NULL) {
2892 rs = row->next;
2893 add_to_free(cur, row);
2894 row = rs;
2895 cur->row_count--;
2897 break;
2899 case REQ_CLR_FIELD:
2900 row = cur->lines->next;
2901 cur->cur_line = cur->lines;
2902 cur->start_line = cur->lines;
2904 while (row != NULL) {
2905 rs = row->next;
2906 add_to_free(cur, row);
2907 row = rs;
2910 cur->lines->string[0] = '\0';
2911 cur->lines->length = 0;
2912 cur->lines->expanded = 0;
2913 cur->row_count = 1;
2914 cur->cursor_ypos = 0;
2915 cur->row_xpos = 0;
2916 _formi_init_field_xpos(cur);
2917 cur->start_char = 0;
2918 break;
2920 case REQ_OVL_MODE:
2921 cur->overlay = 1;
2922 break;
2924 case REQ_INS_MODE:
2925 cur->overlay = 0;
2926 break;
2928 case REQ_SCR_FLINE:
2929 _formi_scroll_fwd(cur, 1);
2930 break;
2932 case REQ_SCR_BLINE:
2933 _formi_scroll_back(cur, 1);
2934 break;
2936 case REQ_SCR_FPAGE:
2937 _formi_scroll_fwd(cur, cur->rows);
2938 break;
2940 case REQ_SCR_BPAGE:
2941 _formi_scroll_back(cur, cur->rows);
2942 break;
2944 case REQ_SCR_FHPAGE:
2945 _formi_scroll_fwd(cur, cur->rows / 2);
2946 break;
2948 case REQ_SCR_BHPAGE:
2949 _formi_scroll_back(cur, cur->rows / 2);
2950 break;
2952 case REQ_SCR_FCHAR:
2953 _formi_hscroll_fwd(cur, row, 1);
2954 break;
2956 case REQ_SCR_BCHAR:
2957 _formi_hscroll_back(cur, row, 1);
2958 break;
2960 case REQ_SCR_HFLINE:
2961 _formi_hscroll_fwd(cur, row, cur->cols);
2962 break;
2964 case REQ_SCR_HBLINE:
2965 _formi_hscroll_back(cur, row, cur->cols);
2966 break;
2968 case REQ_SCR_HFHALF:
2969 _formi_hscroll_fwd(cur, row, cur->cols / 2);
2970 break;
2972 case REQ_SCR_HBHALF:
2973 _formi_hscroll_back(cur, row, cur->cols / 2);
2974 break;
2976 default:
2977 return 0;
2980 #ifdef DEBUG
2981 fprintf(dbg,
2982 "exit: cursor_xpos=%d, row_xpos=%d, start_char=%d, length=%d, allocated=%d\n",
2983 cur->cursor_xpos, cur->row_xpos, cur->start_char,
2984 cur->cur_line->length, cur->cur_line->allocated);
2985 fprintf(dbg, "exit: start_line=%p, ypos=%d\n", cur->start_line,
2986 cur->cursor_ypos);
2987 fprintf(dbg, "exit: string=\"%s\"\n", cur->cur_line->string);
2988 assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX)
2989 && (cur->cursor_xpos >= cur->row_xpos));
2990 #endif
2991 return 1;
2995 * Validate the give character by passing it to any type character
2996 * checking routines, if they exist.
2999 _formi_validate_char(FIELD *field, char c)
3001 int ret_val;
3003 if (field->type == NULL)
3004 return E_OK;
3006 ret_val = E_INVALID_FIELD;
3007 _formi_do_char_validation(field, field->type, c, &ret_val);
3009 return ret_val;
3014 * Perform the validation of the character, invoke all field_type validation
3015 * routines. If the field is ok then update ret_val to E_OK otherwise
3016 * ret_val is not changed.
3018 static void
3019 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
3021 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
3022 _formi_do_char_validation(field, type->link->next, c, ret_val);
3023 _formi_do_char_validation(field, type->link->prev, c, ret_val);
3024 } else {
3025 if (type->char_check == NULL)
3026 *ret_val = E_OK;
3027 else {
3028 if (type->char_check((int)(unsigned char) c,
3029 field->args) == TRUE)
3030 *ret_val = E_OK;
3036 * Validate the current field. If the field validation returns success then
3037 * return E_OK otherwise return E_INVALID_FIELD.
3041 _formi_validate_field(FORM *form)
3043 FIELD *cur;
3044 int ret_val, count;
3047 if ((form == NULL) || (form->fields == NULL) ||
3048 (form->fields[0] == NULL))
3049 return E_INVALID_FIELD;
3051 cur = form->fields[form->cur_field];
3054 * Sync the buffer if it has been modified so the field
3055 * validation routines can use it and because this is
3056 * the correct behaviour according to AT&T implementation.
3058 if ((cur->buf0_status == TRUE)
3059 && ((ret_val = _formi_sync_buffer(cur)) != E_OK))
3060 return ret_val;
3063 * If buffer is untouched then the string pointer may be
3064 * NULL, see if this is ok or not.
3066 if (cur->buffers[0].string == NULL) {
3067 if ((cur->opts & O_NULLOK) == O_NULLOK)
3068 return E_OK;
3069 else
3070 return E_INVALID_FIELD;
3073 count = _formi_skip_blanks(cur->buffers[0].string, 0);
3075 /* check if we have a null field, depending on the nullok flag
3076 * this may be acceptable or not....
3078 if (cur->buffers[0].string[count] == '\0') {
3079 if ((cur->opts & O_NULLOK) == O_NULLOK)
3080 return E_OK;
3081 else
3082 return E_INVALID_FIELD;
3085 /* check if an unmodified field is ok */
3086 if (cur->buf0_status == 0) {
3087 if ((cur->opts & O_PASSOK) == O_PASSOK)
3088 return E_OK;
3089 else
3090 return E_INVALID_FIELD;
3093 /* if there is no type then just accept the field */
3094 if (cur->type == NULL)
3095 return E_OK;
3097 ret_val = E_INVALID_FIELD;
3098 _formi_do_validation(cur, cur->type, &ret_val);
3100 return ret_val;
3104 * Perform the validation of the field, invoke all field_type validation
3105 * routines. If the field is ok then update ret_val to E_OK otherwise
3106 * ret_val is not changed.
3108 static void
3109 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
3111 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
3112 _formi_do_validation(field, type->link->next, ret_val);
3113 _formi_do_validation(field, type->link->prev, ret_val);
3114 } else {
3115 if (type->field_check == NULL)
3116 *ret_val = E_OK;
3117 else {
3118 if (type->field_check(field, field_buffer(field, 0))
3119 == TRUE)
3120 *ret_val = E_OK;
3126 * Select the next/previous choice for the field, the driver command
3127 * selecting the direction will be passed in c. Return 1 if a choice
3128 * selection succeeded, 0 otherwise.
3131 _formi_field_choice(FORM *form, int c)
3133 FIELDTYPE *type;
3134 FIELD *field;
3136 if ((form == NULL) || (form->fields == NULL) ||
3137 (form->fields[0] == NULL) ||
3138 (form->fields[form->cur_field]->type == NULL))
3139 return 0;
3141 field = form->fields[form->cur_field];
3142 type = field->type;
3144 switch (c) {
3145 case REQ_NEXT_CHOICE:
3146 if (type->next_choice == NULL)
3147 return 0;
3148 else
3149 return type->next_choice(field,
3150 field_buffer(field, 0));
3152 case REQ_PREV_CHOICE:
3153 if (type->prev_choice == NULL)
3154 return 0;
3155 else
3156 return type->prev_choice(field,
3157 field_buffer(field, 0));
3159 default: /* should never happen! */
3160 return 0;
3165 * Update the fields if they have changed. The parameter old has the
3166 * previous current field as the current field may have been updated by
3167 * the driver. Return 1 if the form page needs updating.
3171 _formi_update_field(FORM *form, int old_field)
3173 int cur, i;
3175 cur = form->cur_field;
3177 if (old_field != cur) {
3178 if (!((cur >= form->page_starts[form->page].first) &&
3179 (cur <= form->page_starts[form->page].last))) {
3180 /* not on same page any more */
3181 for (i = 0; i < form->max_page; i++) {
3182 if ((form->page_starts[i].in_use == 1) &&
3183 (form->page_starts[i].first <= cur) &&
3184 (form->page_starts[i].last >= cur)) {
3185 form->page = i;
3186 return 1;
3192 _formi_redraw_field(form, old_field);
3193 _formi_redraw_field(form, form->cur_field);
3194 return 0;
3198 * Compare function for the field sorting
3201 static int
3202 field_sort_compare(const void *one, const void *two)
3204 const FIELD *a, *b;
3205 int tl;
3207 /* LINTED const castaway; we don't modify these! */
3208 a = (const FIELD *) *((const FIELD **) one);
3209 b = (const FIELD *) *((const FIELD **) two);
3211 if (a == NULL)
3212 return 1;
3214 if (b == NULL)
3215 return -1;
3218 * First check the page, we want the fields sorted by page.
3221 if (a->page != b->page)
3222 return ((a->page > b->page)? 1 : -1);
3224 tl = _formi_top_left(a->parent, a->index, b->index);
3227 * sort fields left to right, top to bottom so the top left is
3228 * the lesser value....
3230 return ((tl == a->index)? -1 : 1);
3234 * Sort the fields in a form ready for driver traversal.
3236 void
3237 _formi_sort_fields(FORM *form)
3239 FIELD **sort_area;
3240 int i;
3242 CIRCLEQ_INIT(&form->sorted_fields);
3244 if ((sort_area = (FIELD **) malloc(sizeof(FIELD *) * form->field_count))
3245 == NULL)
3246 return;
3248 bcopy(form->fields, sort_area,
3249 (size_t) (sizeof(FIELD *) * form->field_count));
3250 qsort(sort_area, (size_t) form->field_count, sizeof(FIELD *),
3251 field_sort_compare);
3253 for (i = 0; i < form->field_count; i++)
3254 CIRCLEQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
3256 free(sort_area);
3260 * Set the neighbours for all the fields in the given form.
3262 void
3263 _formi_stitch_fields(FORM *form)
3265 int above_row, below_row, end_above, end_below, cur_row, real_end;
3266 FIELD *cur, *above, *below;
3269 * check if the sorted fields circle queue is empty, just
3270 * return if it is.
3272 if (CIRCLEQ_EMPTY(&form->sorted_fields))
3273 return;
3275 /* initially nothing is above..... */
3276 above_row = -1;
3277 end_above = TRUE;
3278 above = NULL;
3280 /* set up the first field as the current... */
3281 cur = CIRCLEQ_FIRST(&form->sorted_fields);
3282 cur_row = cur->form_row;
3284 /* find the first field on the next row if any */
3285 below = CIRCLEQ_NEXT(cur, glue);
3286 below_row = -1;
3287 end_below = TRUE;
3288 real_end = TRUE;
3289 while (below != (void *)&form->sorted_fields) {
3290 if (below->form_row != cur_row) {
3291 below_row = below->form_row;
3292 end_below = FALSE;
3293 real_end = FALSE;
3294 break;
3296 below = CIRCLEQ_NEXT(below, glue);
3299 /* walk the sorted fields, setting the neighbour pointers */
3300 while (cur != (void *) &form->sorted_fields) {
3301 if (cur == CIRCLEQ_FIRST(&form->sorted_fields))
3302 cur->left = NULL;
3303 else
3304 cur->left = CIRCLEQ_PREV(cur, glue);
3306 if (cur == CIRCLEQ_LAST(&form->sorted_fields))
3307 cur->right = NULL;
3308 else
3309 cur->right = CIRCLEQ_NEXT(cur, glue);
3311 if (end_above == TRUE)
3312 cur->up = NULL;
3313 else {
3314 cur->up = above;
3315 above = CIRCLEQ_NEXT(above, glue);
3316 if (above_row != above->form_row) {
3317 end_above = TRUE;
3318 above_row = above->form_row;
3322 if (end_below == TRUE)
3323 cur->down = NULL;
3324 else {
3325 cur->down = below;
3326 below = CIRCLEQ_NEXT(below, glue);
3327 if (below == (void *) &form->sorted_fields) {
3328 end_below = TRUE;
3329 real_end = TRUE;
3330 } else if (below_row != below->form_row) {
3331 end_below = TRUE;
3332 below_row = below->form_row;
3336 cur = CIRCLEQ_NEXT(cur, glue);
3337 if ((cur != (void *) &form->sorted_fields)
3338 && (cur_row != cur->form_row)) {
3339 cur_row = cur->form_row;
3340 if (end_above == FALSE) {
3341 for (; above != CIRCLEQ_FIRST(&form->sorted_fields);
3342 above = CIRCLEQ_NEXT(above, glue)) {
3343 if (above->form_row != above_row) {
3344 above_row = above->form_row;
3345 break;
3348 } else if (above == NULL) {
3349 above = CIRCLEQ_FIRST(&form->sorted_fields);
3350 end_above = FALSE;
3351 above_row = above->form_row;
3352 } else
3353 end_above = FALSE;
3355 if (end_below == FALSE) {
3356 while (below_row == below->form_row) {
3357 below = CIRCLEQ_NEXT(below,
3358 glue);
3359 if (below ==
3360 (void *)&form->sorted_fields) {
3361 real_end = TRUE;
3362 end_below = TRUE;
3363 break;
3367 if (below != (void *)&form->sorted_fields)
3368 below_row = below->form_row;
3369 } else if (real_end == FALSE)
3370 end_below = FALSE;
3377 * Calculate the length of the displayed line allowing for any tab
3378 * characters that need to be expanded. We assume that the tab stops
3379 * are 8 characters apart. The parameters start and end are the
3380 * character positions in the string str we want to get the length of,
3381 * the function returns the number of characters from the start
3382 * position to the end position that should be displayed after any
3383 * intervening tabs have been expanded.
3386 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end)
3388 int len, start_len, i;
3390 /* if we have a null string then there is no length */
3391 if (str[0] == '\0')
3392 return 0;
3394 len = 0;
3395 start_len = 0;
3398 * preceding tabs affect the length tabs in the span, so
3399 * we need to calculate the length including the stuff before
3400 * start and then subtract off the unwanted bit.
3402 for (i = 0; i <= end; i++) {
3403 if (i == start) /* stash preamble length for later */
3404 start_len = len;
3406 if (str[i] == '\0')
3407 break;
3409 if (str[i] == '\t')
3410 len = len - (len % 8) + 8;
3411 else
3412 len++;
3415 #ifdef DEBUG
3416 if (dbg != NULL) {
3417 fprintf(dbg,
3418 "tab_expanded: start=%d, end=%d, expanded=%d (diff=%d)\n",
3419 start, end, (len - start_len), (end - start));
3421 #endif
3423 return (len - start_len);
3427 * Calculate the tab stops on a given line in the field and set up
3428 * the tabs list with the results. We do this by scanning the line for tab
3429 * characters and if one is found, noting the position and the number of
3430 * characters to get to the next tab stop. This information is kept to
3431 * make manipulating the field (scrolling and so on) easier to handle.
3433 void
3434 _formi_calculate_tabs(_FORMI_FIELD_LINES *row)
3436 _formi_tab_t *ts = row->tabs, *old_ts = NULL, **tsp;
3437 int i, j;
3440 * If the line already has tabs then invalidate them by
3441 * walking the list and killing the in_use flag.
3443 for (; ts != NULL; ts = ts->fwd)
3444 ts->in_use = FALSE;
3448 * Now look for tabs in the row and record the info...
3450 tsp = &row->tabs;
3451 for (i = 0, j = 0; i < row->length; i++, j++) {
3452 if (row->string[i] == '\t') {
3453 if (*tsp == NULL) {
3454 if ((*tsp = (_formi_tab_t *)
3455 malloc(sizeof(_formi_tab_t))) == NULL)
3456 return;
3457 (*tsp)->back = old_ts;
3458 (*tsp)->fwd = NULL;
3461 (*tsp)->in_use = TRUE;
3462 (*tsp)->pos = i;
3463 (*tsp)->size = 8 - (j % 8);
3464 j += (*tsp)->size - 1;
3465 old_ts = *tsp;
3466 tsp = &(*tsp)->fwd;
3472 * Return the size of the tab padding for a tab character at the given
3473 * position. Return 1 if there is not a tab char entry matching the
3474 * given location.
3476 static int
3477 tab_size(_FORMI_FIELD_LINES *row, unsigned int i)
3479 _formi_tab_t *ts;
3481 ts = row->tabs;
3482 while ((ts != NULL) && (ts->pos != i))
3483 ts = ts->fwd;
3485 if (ts == NULL)
3486 return 1;
3487 else
3488 return ts->size;
3492 * Find the character offset that corresponds to longest tab expanded
3493 * string that will fit into the given window. Walk the string backwards
3494 * evaluating the sizes of any tabs that are in the string. Note that
3495 * using this function on a multi-line window will produce undefined
3496 * results - it is really only required for a single row field.
3498 static int
3499 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window)
3501 int scroll_amt, i;
3502 _formi_tab_t *ts;
3504 /* first find the last tab */
3505 ts = field->lines->tabs;
3508 * unless there are no tabs - just return the window size,
3509 * if there is enough room, otherwise 0.
3511 if (ts == NULL) {
3512 if (field->lines->length < window)
3513 return 0;
3514 else
3515 return field->lines->length - window + 1;
3518 while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE))
3519 ts = ts->fwd;
3522 * now walk backwards finding the first tab that is to the
3523 * left of our starting pos.
3525 while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos))
3526 ts = ts->back;
3528 scroll_amt = 0;
3529 for (i = pos; i >= 0; i--) {
3530 if (field->lines->string[i] == '\t') {
3531 assert((ts != NULL) && (ts->in_use == TRUE));
3532 if (ts->pos == i) {
3533 if ((scroll_amt + ts->size) > window) {
3534 break;
3536 scroll_amt += ts->size;
3537 ts = ts->back;
3539 else
3540 assert(ts->pos == i);
3541 } else {
3542 scroll_amt++;
3543 if (scroll_amt > window)
3544 break;
3548 return ++i;
3552 * Return the position of the last character that will fit into the
3553 * given width after tabs have been expanded for a given row of a given
3554 * field.
3556 static unsigned int
3557 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int width)
3559 unsigned int pos, len, row_pos;
3560 _formi_tab_t *ts;
3562 ts = row->tabs;
3563 pos = 0;
3564 len = 0;
3565 row_pos = 0;
3567 if (width == 0)
3568 return 0;
3570 while ((len < width) && (pos < row->length)) {
3571 if (row->string[pos] == '\t') {
3572 assert((ts != NULL) && (ts->in_use == TRUE));
3573 if (ts->pos == row_pos) {
3574 if ((len + ts->size) > width)
3575 break;
3576 len += ts->size;
3577 ts = ts->fwd;
3579 else
3580 assert(ts->pos == row_pos);
3581 } else
3582 len++;
3583 pos++;
3584 row_pos++;
3587 if (pos > 0)
3588 pos--;
3589 return pos;
3593 * Sync the field line structures with the contents of buffer 0 for that
3594 * field. We do this by walking all the line structures and concatenating
3595 * all the strings into one single string in buffer 0.
3598 _formi_sync_buffer(FIELD *field)
3600 _FORMI_FIELD_LINES *line;
3601 char *nstr, *tmp;
3602 unsigned length;
3604 if (field->lines == NULL)
3605 return E_BAD_ARGUMENT;
3607 if (field->lines->string == NULL)
3608 return E_BAD_ARGUMENT;
3611 * init nstr up front, just in case there are no line contents,
3612 * this could happen if the field just contains hard returns.
3614 if ((nstr = malloc(sizeof(char))) == NULL)
3615 return E_SYSTEM_ERROR;
3616 nstr[0] = '\0';
3618 line = field->lines;
3619 length = 1; /* allow for terminating null */
3621 while (line != NULL) {
3622 if (line->length != 0) {
3623 if ((tmp = realloc(nstr,
3624 (size_t) (length + line->length)))
3625 == NULL) {
3626 if (nstr != NULL)
3627 free(nstr);
3628 return (E_SYSTEM_ERROR);
3631 nstr = tmp;
3632 strcat(nstr, line->string);
3633 length += line->length;
3636 line = line->next;
3639 if (field->buffers[0].string != NULL)
3640 free(field->buffers[0].string);
3641 field->buffers[0].allocated = length;
3642 field->buffers[0].length = length - 1;
3643 field->buffers[0].string = nstr;
3644 return E_OK;