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