internals.c revision 1.23 1 /* $NetBSD: internals.c,v 1.23 2002/05/20 15:00:11 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 for (i = 0; i < cur->cols; i++) {
1250 waddch(form->scrwin, cur->pad);
1251 }
1252 }
1253
1254 return;
1255 }
1256
1257 /*
1258 * Add the correct number of the given character to simulate a tab
1259 * in the field.
1260 */
1261 static int
1262 add_tab(FORM *form, FIELD *field, unsigned row, unsigned int i, char c)
1263 {
1264 int j;
1265 _formi_tab_t *ts = field->lines[row].tabs;
1266
1267 while ((ts != NULL) && (ts->pos != i))
1268 ts = ts->fwd;
1269
1270 #ifdef DEBUG
1271 assert(ts != NULL);
1272 #endif
1273
1274 for (j = 0; j < ts->size; j++)
1275 waddch(form->scrwin, c);
1276
1277 return ts->size;
1278 }
1279
1280
1281 /*
1282 * Display the fields attached to the form that are on the current page
1283 * on the screen.
1284 *
1285 */
1286 int
1287 _formi_draw_page(FORM *form)
1288 {
1289 int i;
1290
1291 if (form->page_starts[form->page].in_use == 0)
1292 return E_BAD_ARGUMENT;
1293
1294 wclear(form->scrwin);
1295
1296 for (i = form->page_starts[form->page].first;
1297 i <= form->page_starts[form->page].last; i++)
1298 _formi_redraw_field(form, i);
1299
1300 return E_OK;
1301 }
1302
1303 /*
1304 * Add the character c at the position pos in buffer 0 of the given field
1305 */
1306 int
1307 _formi_add_char(FIELD *field, unsigned int pos, char c)
1308 {
1309 char *new, old_c;
1310 unsigned int new_size;
1311 int status;
1312
1313 /*
1314 * If buffer has not had a string before, set it to a blank
1315 * string. Everything should flow from there....
1316 */
1317 if (field->buffers[0].string == NULL) {
1318 set_field_buffer(field, 0, "");
1319 }
1320
1321 if (_formi_validate_char(field, c) != E_OK) {
1322 #ifdef DEBUG
1323 fprintf(dbg, "add_char: char %c failed char validation\n", c);
1324 #endif
1325 return E_INVALID_FIELD;
1326 }
1327
1328 if ((c == '\t') && (field->cols <= 8)) {
1329 #ifdef DEBUG
1330 fprintf(dbg, "add_char: field too small for a tab\n");
1331 #endif
1332 return E_NO_ROOM;
1333 }
1334
1335 #ifdef DEBUG
1336 fprintf(dbg, "add_char: pos=%d, char=%c\n", pos, c);
1337 fprintf(dbg, "add_char enter: xpos=%d, row_pos=%d, start=%d\n",
1338 field->cursor_xpos, field->row_xpos, field->start_char);
1339 fprintf(dbg, "add_char enter: length=%d(%d), allocated=%d\n",
1340 field->buffers[0].length, strlen(field->buffers[0].string),
1341 field->buffers[0].allocated);
1342 fprintf(dbg, "add_char enter: %s\n", field->buffers[0].string);
1343 fprintf(dbg, "add_char enter: buf0_status=%d\n", field->buf0_status);
1344 #endif
1345 if (((field->opts & O_BLANK) == O_BLANK) &&
1346 (field->buf0_status == FALSE) &&
1347 ((field->row_xpos + field->start_char) == 0)) {
1348 field->buffers[0].length = 0;
1349 field->buffers[0].string[0] = '\0';
1350 pos = 0;
1351 field->start_char = 0;
1352 field->start_line = 0;
1353 field->row_count = 1;
1354 field->row_xpos = 0;
1355 field->cursor_xpos = 0;
1356 field->cursor_ypos = 0;
1357 field->lines[0].start = 0;
1358 field->lines[0].end = 0;
1359 field->lines[0].length = 0;
1360 }
1361
1362
1363 if ((field->overlay == 0)
1364 || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
1365 /* first check if the field can have more chars...*/
1366 if (check_field_size(field) == FALSE)
1367 return E_REQUEST_DENIED;
1368
1369 if (field->buffers[0].length + 1
1370 >= field->buffers[0].allocated) {
1371 new_size = field->buffers[0].allocated + 64
1372 - (field->buffers[0].allocated % 64);
1373 if ((new = (char *) realloc(field->buffers[0].string,
1374 new_size )) == NULL)
1375 return E_SYSTEM_ERROR;
1376 field->buffers[0].allocated = new_size;
1377 field->buffers[0].string = new;
1378 }
1379 }
1380
1381 if ((field->overlay == 0) && (field->buffers[0].length > pos)) {
1382 bcopy(&field->buffers[0].string[pos],
1383 &field->buffers[0].string[pos + 1],
1384 field->buffers[0].length - pos + 1);
1385 }
1386
1387 old_c = field->buffers[0].string[pos];
1388 field->buffers[0].string[pos] = c;
1389 if (pos >= field->buffers[0].length) {
1390 /* make sure the string is terminated if we are at the
1391 * end of the string, the terminator would be missing
1392 * if we are are at the end of the field.
1393 */
1394 field->buffers[0].string[pos + 1] = '\0';
1395 }
1396
1397 /* only increment the length if we are inserting characters
1398 * OR if we are at the end of the field in overlay mode.
1399 */
1400 if ((field->overlay == 0)
1401 || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
1402 field->buffers[0].length++;
1403 bump_lines(field, (int) pos, 1, TRUE);
1404 } else if (field->overlay == 1)
1405 bump_lines(field, (int) pos, 0, TRUE);
1406
1407 new_size = find_cur_line(field, pos);
1408 _formi_calculate_tabs(field, new_size);
1409
1410 /* wrap the field, if needed */
1411 status = _formi_wrap_field(field, pos);
1412
1413 /* just in case the row we are on wrapped */
1414 new_size = find_cur_line(field, pos);
1415
1416 /*
1417 * check the wrap worked or that we have not exceeded the
1418 * max field size - this can happen if the field is re-wrapped
1419 * and the row count is increased past the set limit.
1420 */
1421 if ((status != E_OK) || (check_field_size(field) == FALSE)) {
1422 if ((field->overlay == 0)
1423 || ((field->overlay == 1)
1424 && (pos >= field->buffers[0].length))) {
1425 /*
1426 * wrap failed for some reason, back out the
1427 * char insert
1428 */
1429 bcopy(&field->buffers[0].string[pos + 1],
1430 &field->buffers[0].string[pos],
1431 field->buffers[0].length - pos);
1432 field->buffers[0].length--;
1433 bump_lines(field, (int) pos, -1, TRUE);
1434 if (pos > 0)
1435 pos--;
1436 } else if (field->overlay == 1) {
1437 /* back out character overlay */
1438 field->buffers[0].string[pos] = old_c;
1439 }
1440
1441 new_size = find_cur_line(field, pos);
1442 _formi_calculate_tabs(field, new_size);
1443
1444 _formi_wrap_field(field, pos);
1445 /*
1446 * If we are here then either the status is bad or we
1447 * simply ran out of room. If the status is E_OK then
1448 * we ran out of room, let the form driver know this.
1449 */
1450 if (status == E_OK)
1451 status = E_REQUEST_DENIED;
1452
1453 } else {
1454 field->buf0_status = TRUE;
1455 if ((field->rows + field->nrows) == 1) {
1456 if ((field->cursor_xpos < (field->cols - 1)) ||
1457 ((field->opts & O_STATIC) != O_STATIC)) {
1458 field->row_xpos++;
1459 field->cursor_xpos =
1460 _formi_tab_expanded_length(
1461 field->buffers[0].string,
1462 field->start_char,
1463 field->row_xpos
1464 + field->start_char);
1465 if ((field->start_char + field->row_xpos)
1466 < field->buffers[0].length)
1467 field->cursor_xpos--;
1468 }
1469
1470 if (field->cursor_xpos > (field->cols - 1)) {
1471 field->start_char =
1472 tab_fit_window(field,
1473 field->start_char + field->row_xpos,
1474 field->cols);
1475 field->row_xpos = pos - field->start_char + 1;
1476 field->cursor_xpos =
1477 _formi_tab_expanded_length(
1478 field->buffers[0].string,
1479 field->start_char,
1480 field->row_xpos
1481 + field->start_char - 1);
1482 }
1483 } else {
1484 if (new_size >= field->rows) {
1485 field->cursor_ypos = field->rows - 1;
1486 field->start_line = field->row_count
1487 - field->cursor_ypos - 1;
1488 } else
1489 field->cursor_ypos = new_size;
1490
1491 if ((field->lines[new_size].start) <= (pos + 1)) {
1492 field->row_xpos = pos
1493 - field->lines[new_size].start + 1;
1494 field->cursor_xpos =
1495 _formi_tab_expanded_length(
1496 &field->buffers[0].string[
1497 field->lines[new_size].start], 0,
1498 field->row_xpos - 1);
1499 } else {
1500 field->row_xpos = 0;
1501 field->cursor_xpos = 0;
1502 }
1503
1504 /*
1505 * Annoying corner case - if we are right in
1506 * the bottom right corner of the field we
1507 * need to scroll the field one line so the
1508 * cursor is positioned correctly in the
1509 * field.
1510 */
1511 if ((field->cursor_xpos >= field->cols) &&
1512 (field->cursor_ypos == (field->rows - 1))) {
1513 field->cursor_ypos--;
1514 field->start_line++;
1515 }
1516 }
1517 }
1518
1519 #ifdef DEBUG
1520 assert((field->cursor_xpos <= field->cols)
1521 && (field->cursor_ypos < 400000)
1522 && (field->start_line < 400000));
1523
1524 fprintf(dbg, "add_char exit: xpos=%d, row_pos=%d, start=%d\n",
1525 field->cursor_xpos, field->row_xpos, field->start_char);
1526 fprintf(dbg, "add_char_exit: length=%d(%d), allocated=%d\n",
1527 field->buffers[0].length, strlen(field->buffers[0].string),
1528 field->buffers[0].allocated);
1529 fprintf(dbg, "add_char exit: ypos=%d, start_line=%d\n",
1530 field->cursor_ypos, field->start_line);
1531 fprintf(dbg,"add_char exit: %s\n", field->buffers[0].string);
1532 fprintf(dbg, "add_char exit: buf0_status=%d\n", field->buf0_status);
1533 fprintf(dbg, "add_char exit: status = %s\n",
1534 (status == E_OK)? "OK" : "FAILED");
1535 #endif
1536 return status;
1537 }
1538
1539 /*
1540 * Manipulate the text in a field, this takes the given form and performs
1541 * the passed driver command on the current text field. Returns 1 if the
1542 * text field was modified.
1543 */
1544 int
1545 _formi_manipulate_field(FORM *form, int c)
1546 {
1547 FIELD *cur;
1548 char *str, saved;
1549 unsigned int i, start, end, pos, row, status, old_count, size;
1550 unsigned int old_xpos, old_row_pos;
1551 int len;
1552
1553 cur = form->fields[form->cur_field];
1554
1555 #ifdef DEBUG
1556 fprintf(dbg, "entry: request is REQ_%s\n", reqs[c - REQ_MIN_REQUEST]);
1557 fprintf(dbg,
1558 "entry: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
1559 cur->cursor_xpos, cur->row_xpos, cur->start_char,
1560 cur->buffers[0].length, cur->buffers[0].allocated);
1561 fprintf(dbg, "entry: start_line=%d, ypos=%d\n", cur->start_line,
1562 cur->cursor_ypos);
1563 fprintf(dbg, "entry: string=");
1564 if (cur->buffers[0].string == NULL)
1565 fprintf(dbg, "(null)\n");
1566 else
1567 fprintf(dbg, "\"%s\"\n", cur->buffers[0].string);
1568 #endif
1569
1570 /* Cannot manipulate a null string! */
1571 if (cur->buffers[0].string == NULL)
1572 return E_REQUEST_DENIED;
1573
1574 row = cur->start_line + cur->cursor_ypos;
1575 saved = cur->buffers[0].string[cur->start_char + cur->row_xpos
1576 + cur->lines[row].start];
1577
1578 switch (c) {
1579 case REQ_RIGHT_CHAR:
1580 /*
1581 * The right_char request performs the same function
1582 * as the next_char request except that the cursor is
1583 * not wrapped if it is at the end of the line, so
1584 * check if the cursor is at the end of the line and
1585 * deny the request otherwise just fall through to
1586 * the next_char request handler.
1587 */
1588 if (cur->cursor_xpos >= cur->lines[row].length - 1)
1589 return E_REQUEST_DENIED;
1590
1591 /* FALLTHRU */
1592
1593 case REQ_NEXT_CHAR:
1594 /* for a dynamic field allow an offset of one more
1595 * char so we can insert chars after end of string.
1596 * Static fields cannot do this so deny request if
1597 * cursor is at the end of the field.
1598 */
1599 if (((cur->opts & O_STATIC) == O_STATIC) &&
1600 (cur->row_xpos == cur->cols - 1) &&
1601 ((cur->rows + cur->nrows) == 1))
1602 return E_REQUEST_DENIED;
1603
1604 if ((cur->row_xpos + cur->start_char + 1)
1605 > cur->buffers[0].length)
1606 return E_REQUEST_DENIED;
1607
1608 if ((cur->rows + cur->nrows) == 1) {
1609 if (saved == '\t')
1610 cur->cursor_xpos += tab_size(cur, 0,
1611 cur->row_xpos
1612 + cur->start_char);
1613 else
1614 cur->cursor_xpos++;
1615 cur->row_xpos++;
1616 if (cur->cursor_xpos >= cur->cols - 1) {
1617 pos = cur->row_xpos + cur->start_char;
1618 cur->start_char =
1619 tab_fit_window(cur,
1620 cur->start_char + cur->row_xpos,
1621 cur->cols);
1622 cur->row_xpos = pos - cur->start_char;
1623 cur->cursor_xpos =
1624 _formi_tab_expanded_length(
1625 cur->buffers[0].string,
1626 cur->start_char,
1627 cur->row_xpos
1628 + cur->start_char);
1629 if ((cur->row_xpos + cur->start_char) <
1630 cur->buffers[0].length)
1631 cur->cursor_xpos--;
1632 }
1633 } else {
1634 if (cur->cursor_xpos >= (cur->lines[row].length - 1)) {
1635 if ((row + 1) >= cur->row_count)
1636 return E_REQUEST_DENIED;
1637
1638 cur->cursor_xpos = 0;
1639 cur->row_xpos = 0;
1640 if (cur->cursor_ypos == (cur->rows - 1))
1641 cur->start_line++;
1642 else
1643 cur->cursor_ypos++;
1644 } else {
1645 old_xpos = cur->cursor_xpos;
1646 old_row_pos = cur->row_xpos;
1647 if (saved == '\t')
1648 cur->cursor_xpos += tab_size(cur,
1649 cur->lines[row].start,
1650 cur->row_xpos);
1651 else
1652 cur->cursor_xpos++;
1653 cur->row_xpos++;
1654 if (cur->cursor_xpos
1655 >= cur->lines[row].length) {
1656 if ((row + 1) >= cur->row_count) {
1657 cur->cursor_xpos = old_xpos;
1658 cur->row_xpos = old_row_pos;
1659 return E_REQUEST_DENIED;
1660 }
1661
1662 cur->cursor_xpos = 0;
1663 cur->row_xpos = 0;
1664 if (cur->cursor_ypos
1665 == (cur->rows - 1))
1666 cur->start_line++;
1667 else
1668 cur->cursor_ypos++;
1669 }
1670 }
1671 }
1672
1673 break;
1674
1675 case REQ_LEFT_CHAR:
1676 /*
1677 * The behaviour of left_char is the same as prev_char
1678 * except that the cursor will not wrap if it has
1679 * reached the LHS of the field, so just check this
1680 * and fall through if we are not at the LHS.
1681 */
1682 if (cur->cursor_xpos == 0)
1683 return E_REQUEST_DENIED;
1684
1685 /* FALLTHRU */
1686 case REQ_PREV_CHAR:
1687 if ((cur->rows + cur->nrows) == 1) {
1688 if (cur->row_xpos == 0) {
1689 if (cur->start_char > 0)
1690 cur->start_char--;
1691 else
1692 return E_REQUEST_DENIED;
1693 } else {
1694 cur->row_xpos--;
1695 cur->cursor_xpos =
1696 _formi_tab_expanded_length(
1697 cur->buffers[0].string,
1698 cur->start_char,
1699 cur->row_xpos
1700 + cur->start_char) - 1;
1701 }
1702 } else {
1703 if ((cur->cursor_xpos == 0) &&
1704 (cur->cursor_ypos == 0) &&
1705 (cur->start_line == 0))
1706 return E_REQUEST_DENIED;
1707
1708 row = cur->start_line + cur->cursor_ypos;
1709 pos = cur->lines[row].start + cur->row_xpos;
1710 if ((pos >= cur->buffers[0].length) && (pos > 0))
1711 pos--;
1712
1713 if (cur->cursor_xpos > 0) {
1714 if (cur->buffers[0].string[pos] == '\t') {
1715 size = tab_size(cur,
1716 cur->lines[row].start,
1717 pos
1718 - cur->lines[row].start);
1719 if (size > cur->cursor_xpos) {
1720 cur->cursor_xpos = 0;
1721 cur->row_xpos = 0;
1722 } else {
1723 cur->row_xpos--;
1724 cur->cursor_xpos -= size;
1725 }
1726 } else {
1727 cur->cursor_xpos--;
1728 cur->row_xpos--;
1729 }
1730 } else {
1731 if (cur->cursor_ypos > 0)
1732 cur->cursor_ypos--;
1733 else
1734 cur->start_line--;
1735 row = cur->start_line + cur->cursor_ypos;
1736 cur->cursor_xpos = cur->lines[row].length - 1;
1737 cur->row_xpos = cur->lines[row].end -
1738 cur->lines[row].start;
1739 }
1740 }
1741
1742 break;
1743
1744 case REQ_DOWN_CHAR:
1745 /*
1746 * The down_char request has the same functionality as
1747 * the next_line request excepting that the field is not
1748 * scrolled if the cursor is at the bottom of the field.
1749 * Check to see if the cursor is at the bottom of the field
1750 * and if it is then deny the request otherwise fall
1751 * through to the next_line handler.
1752 */
1753 if (cur->cursor_ypos >= cur->rows - 1)
1754 return E_REQUEST_DENIED;
1755
1756 /* FALLTHRU */
1757
1758 case REQ_NEXT_LINE:
1759 if ((cur->start_line + cur->cursor_ypos + 1) >= cur->row_count)
1760 return E_REQUEST_DENIED;
1761
1762 if ((cur->cursor_ypos + 1) >= cur->rows) {
1763 cur->start_line++;
1764 } else
1765 cur->cursor_ypos++;
1766 row = cur->cursor_ypos + cur->start_line;
1767 cur->row_xpos = tab_fit_len(cur, row, cur->cursor_xpos)
1768 - cur->lines[row].start;
1769 cur->cursor_xpos =
1770 _formi_tab_expanded_length(
1771 &cur->buffers[0].string[cur->lines[row].start],
1772 0, cur->row_xpos);
1773 break;
1774
1775 case REQ_UP_CHAR:
1776 /*
1777 * The up_char request has the same functionality as
1778 * the prev_line request excepting the field is not
1779 * scrolled, check if the cursor is at the top of the
1780 * field, if it is deny the request otherwise fall
1781 * through to the prev_line handler.
1782 */
1783 if (cur->cursor_ypos == 0)
1784 return E_REQUEST_DENIED;
1785
1786 /* FALLTHRU */
1787
1788 case REQ_PREV_LINE:
1789 if (cur->cursor_ypos == 0) {
1790 if (cur->start_line == 0)
1791 return E_REQUEST_DENIED;
1792 cur->start_line--;
1793 } else
1794 cur->cursor_ypos--;
1795 row = cur->cursor_ypos + cur->start_line;
1796 cur->row_xpos = tab_fit_len(cur, row, cur->cursor_xpos + 1)
1797 - cur->lines[row].start;
1798 cur->cursor_xpos =
1799 _formi_tab_expanded_length(
1800 &cur->buffers[0].string[cur->lines[row].start],
1801 0, cur->row_xpos) - 1;
1802 break;
1803
1804 case REQ_NEXT_WORD:
1805 start = cur->lines[cur->start_line + cur->cursor_ypos].start
1806 + cur->row_xpos + cur->start_char;
1807 str = cur->buffers[0].string;
1808
1809 start = find_eow(str, start);
1810
1811 /* check if we hit the end */
1812 if (str[start] == '\0')
1813 return E_REQUEST_DENIED;
1814
1815 /* otherwise we must have found the start of a word...*/
1816 if ((cur->rows + cur->nrows) == 1) {
1817 /* single line field */
1818 size = _formi_tab_expanded_length(str,
1819 cur->start_char, start);
1820 if (size < cur->cols) {
1821 cur->row_xpos = start - cur->start_char;
1822 cur->cursor_xpos = size - 1;
1823 } else {
1824 cur->start_char = start;
1825 cur->row_xpos = 0;
1826 cur->cursor_xpos = 0;
1827 }
1828 } else {
1829 /* multiline field */
1830 row = find_cur_line(cur, start);
1831 cur->row_xpos = start - cur->lines[row].start;
1832 cur->cursor_xpos = _formi_tab_expanded_length(
1833 &str[cur->lines[row].start],
1834 0, cur->row_xpos) - 1;
1835 if (row != (cur->start_line + cur->cursor_ypos)) {
1836 if (cur->cursor_ypos == (cur->rows - 1)) {
1837 cur->start_line = row - cur->rows + 1;
1838 } else {
1839 cur->cursor_ypos = row
1840 - cur->start_line;
1841 }
1842 }
1843 }
1844 break;
1845
1846 case REQ_PREV_WORD:
1847 start = cur->start_char + cur->row_xpos
1848 + cur->lines[cur->start_line + cur->cursor_ypos].start;
1849 if (cur->start_char > 0)
1850 start--;
1851
1852 if (start == 0)
1853 return E_REQUEST_DENIED;
1854
1855 str = cur->buffers[0].string;
1856
1857 start = find_sow(str, start);
1858
1859 if ((cur->rows + cur->nrows) == 1) {
1860 /* single line field */
1861 size = _formi_tab_expanded_length(str,
1862 cur->start_char, start);
1863
1864 if (start > cur->start_char) {
1865 cur->row_xpos = start - cur->start_char;
1866 cur->cursor_xpos = size - 1;
1867 } else {
1868 cur->start_char = start;
1869 cur->cursor_xpos = 0;
1870 cur->row_xpos = 0;
1871 }
1872 } else {
1873 /* multiline field */
1874 row = find_cur_line(cur, start);
1875 cur->row_xpos = start - cur->lines[row].start;
1876 cur->cursor_xpos = _formi_tab_expanded_length(
1877 &str[cur->lines[row].start], 0,
1878 cur->row_xpos) - 1;
1879 if (row != (cur->start_line + cur->cursor_ypos)) {
1880 if (cur->cursor_ypos == 0) {
1881 cur->start_line = row;
1882 } else {
1883 if (cur->start_line > row) {
1884 cur->start_line = row;
1885 cur->cursor_ypos = 0;
1886 } else {
1887 cur->cursor_ypos = row -
1888 cur->start_line;
1889 }
1890 }
1891 }
1892 }
1893
1894 break;
1895
1896 case REQ_BEG_FIELD:
1897 cur->start_char = 0;
1898 cur->start_line = 0;
1899 cur->row_xpos = 0;
1900 cur->cursor_xpos = 0;
1901 cur->cursor_ypos = 0;
1902 break;
1903
1904 case REQ_BEG_LINE:
1905 cur->cursor_xpos = 0;
1906 cur->row_xpos = 0;
1907 cur->start_char = 0;
1908 break;
1909
1910 case REQ_END_FIELD:
1911 if (cur->row_count > cur->rows) {
1912 cur->start_line = cur->row_count - cur->rows;
1913 cur->cursor_ypos = cur->rows - 1;
1914 } else {
1915 cur->start_line = 0;
1916 cur->cursor_ypos = cur->row_count - 1;
1917 }
1918
1919 /* we fall through here deliberately, we are on the
1920 * correct row, now we need to get to the end of the
1921 * line.
1922 */
1923 /* FALLTHRU */
1924
1925 case REQ_END_LINE:
1926 row = cur->start_line + cur->cursor_ypos;
1927 start = cur->lines[row].start;
1928 end = cur->lines[row].end;
1929
1930 if ((cur->rows + cur->nrows) == 1) {
1931 if (cur->lines[row].length > cur->cols - 1) {
1932 if ((cur->opts & O_STATIC) != O_STATIC) {
1933 cur->start_char = tab_fit_window(
1934 cur, end, cur->cols) + 1;
1935 } else {
1936 cur->start_char = tab_fit_window(
1937 cur, end, cur->cols);
1938 }
1939 cur->row_xpos = cur->buffers[0].length
1940 - cur->start_char;
1941 cur->cursor_xpos =
1942 _formi_tab_expanded_length(
1943 cur->buffers[0].string,
1944 cur->start_char,
1945 cur->row_xpos + cur->start_char);
1946 } else {
1947 cur->row_xpos = end + 1;
1948 cur->cursor_xpos = _formi_tab_expanded_length(
1949 cur->buffers[0].string,
1950 cur->start_char,
1951 cur->row_xpos + cur->start_char);
1952 if (((cur->opts & O_STATIC) == O_STATIC) &&
1953 ((cur->lines[row].length)
1954 == (cur->cols - 1)))
1955 cur->cursor_xpos--;
1956
1957 cur->start_char = 0;
1958 }
1959 } else {
1960 cur->row_xpos = end - start;
1961 cur->cursor_xpos = cur->lines[row].length - 1;
1962 if (row == (cur->row_count - 1)) {
1963 cur->row_xpos++;
1964 cur->cursor_xpos++;
1965 }
1966 }
1967 break;
1968
1969 case REQ_NEW_LINE:
1970 if ((status = split_line(cur,
1971 cur->start_char + cur->row_xpos)) != E_OK)
1972 return status;
1973 break;
1974
1975 case REQ_INS_CHAR:
1976 if ((status = _formi_add_char(cur, cur->start_char
1977 + cur->row_xpos,
1978 cur->pad)) != E_OK)
1979 return status;
1980 break;
1981
1982 case REQ_INS_LINE:
1983 start = cur->lines[cur->start_line + cur->cursor_ypos].start;
1984 if ((status = split_line(cur, start)) != E_OK)
1985 return status;
1986 break;
1987
1988 case REQ_DEL_CHAR:
1989 if (cur->buffers[0].length == 0)
1990 return E_REQUEST_DENIED;
1991
1992 row = cur->start_line + cur->cursor_ypos;
1993 start = cur->start_char + cur->row_xpos
1994 + cur->lines[row].start;
1995 end = cur->buffers[0].length;
1996 if (start >= cur->buffers[0].length)
1997 return E_REQUEST_DENIED;
1998
1999 if (start == cur->lines[row].end) {
2000 if ((cur->rows + cur->nrows) > 1) {
2001 /*
2002 * If we have more than one row, join the
2003 * next row to make things easier unless
2004 * we are at the end of the string, in
2005 * that case the join would fail but we
2006 * really want to delete the last char
2007 * in the field.
2008 */
2009 if ((cur->row_count > 1)
2010 && (start != (end - 1))) {
2011 if (_formi_join_line(cur,
2012 start,
2013 JOIN_NEXT_NW)
2014 != E_OK) {
2015 return E_REQUEST_DENIED;
2016 }
2017 }
2018 }
2019 }
2020
2021 saved = cur->buffers[0].string[start];
2022 bcopy(&cur->buffers[0].string[start + 1],
2023 &cur->buffers[0].string[start],
2024 (unsigned) end - start + 1);
2025 bump_lines(cur, _FORMI_USE_CURRENT, -1, TRUE);
2026 cur->buffers[0].length--;
2027
2028 /*
2029 * recalculate tabs for a single line field, multiline
2030 * fields will do this when the field is wrapped.
2031 */
2032 if ((cur->rows + cur->nrows) == 1)
2033 _formi_calculate_tabs(cur, 0);
2034 /*
2035 * if we are at the end of the string then back the
2036 * cursor pos up one to stick on the end of the line
2037 */
2038 if (start == cur->buffers[0].length) {
2039 if (cur->buffers[0].length > 1) {
2040 if ((cur->rows + cur->nrows) == 1) {
2041 pos = cur->row_xpos + cur->start_char;
2042 cur->start_char =
2043 tab_fit_window(cur,
2044 cur->start_char + cur->row_xpos,
2045 cur->cols);
2046 cur->row_xpos = pos - cur->start_char
2047 - 1;
2048 cur->cursor_xpos =
2049 _formi_tab_expanded_length(
2050 cur->buffers[0].string,
2051 cur->start_char,
2052 cur->row_xpos
2053 + cur->start_char);
2054 } else {
2055 if (cur->row_xpos == 0) {
2056 if (cur->lines[row].start !=
2057 cur->buffers[0].length) {
2058 if (_formi_join_line(
2059 cur,
2060 cur->lines[row].start,
2061 JOIN_PREV_NW)
2062 != E_OK) {
2063 return E_REQUEST_DENIED;
2064 }
2065 } else {
2066 if (cur->row_count > 1)
2067 cur->row_count--;
2068 }
2069
2070 row = cur->start_line
2071 + cur->cursor_ypos;
2072 if (row > 0)
2073 row--;
2074 }
2075
2076 cur->row_xpos = start
2077 - cur->lines[row].start - 1;
2078 cur->cursor_xpos =
2079 _formi_tab_expanded_length(
2080 &cur->buffers[0].string[cur->lines[row].start],
2081 0, cur->row_xpos);
2082 if ((cur->cursor_xpos > 0)
2083 && (start != (cur->buffers[0].length - 1)))
2084 cur->cursor_xpos--;
2085 if (row >= cur->rows)
2086 cur->start_line = row
2087 - cur->cursor_ypos;
2088 else {
2089 cur->start_line = 0;
2090 cur->cursor_ypos = row;
2091 }
2092 }
2093
2094 start--;
2095 } else {
2096 start = 0;
2097 cur->row_xpos = 0;
2098 cur->cursor_xpos = 0;
2099 }
2100 }
2101
2102 if ((cur->rows + cur->nrows) > 1) {
2103 if (_formi_wrap_field(cur, start) != E_OK) {
2104 bcopy(&cur->buffers[0].string[start],
2105 &cur->buffers[0].string[start + 1],
2106 (unsigned) end - start);
2107 cur->buffers[0].length++;
2108 cur->buffers[0].string[start] = saved;
2109 bump_lines(cur, _FORMI_USE_CURRENT, 1, TRUE);
2110 _formi_wrap_field(cur, start);
2111 return E_REQUEST_DENIED;
2112 }
2113 }
2114 break;
2115
2116 case REQ_DEL_PREV:
2117 if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
2118 && (cur->start_line == 0) && (cur->cursor_ypos == 0))
2119 return E_REQUEST_DENIED;
2120
2121 row = cur->start_line + cur->cursor_ypos;
2122 start = cur->row_xpos + cur->start_char
2123 + cur->lines[row].start;
2124 end = cur->buffers[0].length;
2125
2126 if ((cur->start_char + cur->row_xpos) == 0) {
2127 /*
2128 * Join this line to the next one, but only if
2129 * we are not at the end of the string because
2130 * in that case there are no characters to join.
2131 */
2132 if (cur->lines[row].start != end) {
2133 if (_formi_join_line(cur,
2134 cur->lines[row].start,
2135 JOIN_PREV_NW) != E_OK) {
2136 return E_REQUEST_DENIED;
2137 }
2138 } else {
2139 /* but we do want the row count decremented */
2140 if (cur->row_count > 1)
2141 cur->row_count--;
2142 }
2143 }
2144
2145 saved = cur->buffers[0].string[start - 1];
2146 bcopy(&cur->buffers[0].string[start],
2147 &cur->buffers[0].string[start - 1],
2148 (unsigned) end - start + 1);
2149 bump_lines(cur, (int) start - 1, -1, TRUE);
2150 cur->buffers[0].length--;
2151
2152 if ((cur->rows + cur->nrows) == 1) {
2153 _formi_calculate_tabs(cur, 0);
2154 pos = cur->row_xpos + cur->start_char;
2155 if (pos > 0)
2156 pos--;
2157 cur->start_char =
2158 tab_fit_window(cur,
2159 cur->start_char + cur->row_xpos,
2160 cur->cols);
2161 cur->row_xpos = pos - cur->start_char;
2162 cur->cursor_xpos =
2163 _formi_tab_expanded_length(
2164 cur->buffers[0].string,
2165 cur->start_char,
2166 cur->row_xpos
2167 + cur->start_char);
2168 } else {
2169 pos = start - 1;
2170 if (pos >= cur->buffers[0].length)
2171 pos = cur->buffers[0].length - 1;
2172
2173 if ((_formi_wrap_field(cur, pos) != E_OK)) {
2174 bcopy(&cur->buffers[0].string[start - 1],
2175 &cur->buffers[0].string[start],
2176 (unsigned) end - start);
2177 cur->buffers[0].length++;
2178 cur->buffers[0].string[start - 1] = saved;
2179 bump_lines(cur, (int) start - 1, 1, TRUE);
2180 _formi_wrap_field(cur, pos);
2181 return E_REQUEST_DENIED;
2182 }
2183
2184 row = find_cur_line(cur, pos);
2185 cur->row_xpos = start - cur->lines[row].start - 1;
2186 cur->cursor_xpos = _formi_tab_expanded_length(
2187 &cur->buffers[0].string[cur->lines[row].start],
2188 0, cur->row_xpos);
2189 if ((cur->cursor_xpos > 0)
2190 && (pos != (cur->buffers[0].length - 1)))
2191 cur->cursor_xpos--;
2192
2193 if (row >= cur->rows)
2194 cur->start_line = row - cur->cursor_ypos;
2195 else {
2196 cur->start_line = 0;
2197 cur->cursor_ypos = row;
2198 }
2199 }
2200 break;
2201
2202 case REQ_DEL_LINE:
2203 row = cur->start_line + cur->cursor_ypos;
2204 start = cur->lines[row].start;
2205 end = cur->lines[row].end;
2206 bcopy(&cur->buffers[0].string[end + 1],
2207 &cur->buffers[0].string[start],
2208 (unsigned) cur->buffers[0].length - end + 1);
2209
2210 if (((cur->rows + cur->nrows) == 1) ||
2211 (cur->row_count == 1)) {
2212 /* single line case */
2213 cur->buffers[0].length = 0;
2214 cur->lines[0].end = cur->lines[0].length = 0;
2215 cur->cursor_xpos = cur->row_xpos = 0;
2216 cur->cursor_ypos = 0;
2217 } else {
2218 /* multiline field */
2219 old_count = cur->row_count;
2220 cur->row_count--;
2221 if (cur->row_count == 0)
2222 cur->row_count = 1;
2223
2224 if (cur->row_count > 1)
2225 bcopy(&cur->lines[row + 1],
2226 &cur->lines[row],
2227 (unsigned) (cur->row_count - row)
2228 * sizeof(struct _formi_field_lines));
2229
2230 cur->lines[row].start = start;
2231 len = start - end - 1; /* yes, this is negative */
2232
2233 if (row < (cur->row_count - 1))
2234 bump_lines(cur, (int) start, len, FALSE);
2235 else if (old_count == 1) {
2236 cur->lines[0].end = cur->lines[0].length = 0;
2237 cur->cursor_xpos = 0;
2238 cur->row_xpos = 0;
2239 cur->cursor_ypos = 0;
2240 } else if (cur->row_count == 1) {
2241 cur->lines[0].length = cur->buffers[0].length
2242 + len;
2243 cur->lines[0].end = cur->lines[0].length - 1;
2244 }
2245
2246 cur->buffers[0].length += len;
2247
2248 if (row > (cur->row_count - 1)) {
2249 row--;
2250 if (cur->cursor_ypos == 0) {
2251 if (cur->start_line > 0) {
2252 cur->start_line--;
2253 }
2254 } else {
2255 cur->cursor_ypos--;
2256 }
2257 }
2258
2259 if (old_count > 1) {
2260 if (cur->cursor_xpos > cur->lines[row].length) {
2261 cur->cursor_xpos =
2262 cur->lines[row].length - 1;
2263 cur->row_xpos = cur->lines[row].end;
2264 }
2265
2266 if (row >= cur->rows)
2267 cur->start_line = row
2268 - cur->cursor_ypos;
2269 else {
2270 cur->start_line = 0;
2271 cur->cursor_ypos = row;
2272 }
2273 }
2274 }
2275 break;
2276
2277 case REQ_DEL_WORD:
2278 start = cur->start_char + cur->row_xpos
2279 + cur->lines[row].start;
2280 str = cur->buffers[0].string;
2281
2282 end = find_eow(str, start);
2283
2284 /*
2285 * If not at the start of a word then find the start,
2286 * we cannot blindly call find_sow because this will
2287 * skip back a word if we are already at the start of
2288 * a word.
2289 */
2290 if ((start > 0)
2291 && !(isblank(str[start - 1]) && !isblank(str[start])))
2292 start = find_sow(cur->buffers[0].string, start);
2293 bcopy(&cur->buffers[0].string[end],
2294 &cur->buffers[0].string[start],
2295 (unsigned) cur->buffers[0].length - end + 1);
2296 len = end - start;
2297 cur->buffers[0].length -= len;
2298 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
2299
2300 if ((cur->rows + cur->nrows) > 1) {
2301 row = cur->start_line + cur->cursor_ypos;
2302 if ((row + 1) < cur->row_count) {
2303 /*
2304 * if not on the last row we need to
2305 * join on the next row so the line
2306 * will be re-wrapped.
2307 */
2308 _formi_join_line(cur, cur->lines[row].end,
2309 JOIN_NEXT_NW);
2310 }
2311 _formi_wrap_field(cur, start);
2312 row = find_cur_line(cur, start);
2313 cur->row_xpos = start - cur->lines[row].start;
2314 cur->cursor_xpos = _formi_tab_expanded_length(
2315 &cur->buffers[0].string[cur->lines[row].start],
2316 0, cur->row_xpos);
2317 if ((cur->cursor_xpos > 0)
2318 && (start != (cur->buffers[0].length - 1)))
2319 cur->cursor_xpos--;
2320 } else {
2321 _formi_calculate_tabs(cur, 0);
2322 cur->row_xpos = start - cur->start_char;
2323 if (cur->row_xpos > 0)
2324 cur->row_xpos--;
2325 cur->cursor_xpos = _formi_tab_expanded_length(
2326 cur->buffers[0].string, cur->start_char,
2327 cur->row_xpos);
2328 if (cur->cursor_xpos > 0)
2329 cur->cursor_xpos--;
2330 }
2331 break;
2332
2333 case REQ_CLR_EOL:
2334 row = cur->start_line + cur->cursor_ypos;
2335 start = cur->start_char + cur->cursor_xpos;
2336 end = cur->lines[row].end;
2337 len = end - start;
2338 bcopy(&cur->buffers[0].string[end + 1],
2339 &cur->buffers[0].string[start],
2340 cur->buffers[0].length - end + 1);
2341 cur->buffers[0].length -= len;
2342 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
2343
2344 if (cur->cursor_xpos > cur->lines[row].length) {
2345 cur->row_xpos = cur->lines[row].end;
2346 cur->cursor_xpos = cur->lines[row].length;
2347 }
2348 break;
2349
2350 case REQ_CLR_EOF:
2351 row = cur->start_line + cur->cursor_ypos;
2352 cur->buffers[0].string[cur->start_char
2353 + cur->cursor_xpos] = '\0';
2354 cur->buffers[0].length = strlen(cur->buffers[0].string);
2355 cur->lines[row].end = cur->buffers[0].length;
2356 cur->lines[row].length = cur->lines[row].end
2357 - cur->lines[row].start;
2358
2359 for (i = cur->start_char + cur->row_xpos;
2360 i < cur->buffers[0].length; i++)
2361 cur->buffers[0].string[i] = cur->pad;
2362 break;
2363
2364 case REQ_CLR_FIELD:
2365 cur->buffers[0].string[0] = '\0';
2366 cur->buffers[0].length = 0;
2367 cur->row_count = 1;
2368 cur->start_line = 0;
2369 cur->cursor_ypos = 0;
2370 cur->cursor_xpos = 0;
2371 cur->row_xpos = 0;
2372 cur->start_char = 0;
2373 cur->lines[0].start = 0;
2374 cur->lines[0].end = 0;
2375 cur->lines[0].length = 0;
2376 break;
2377
2378 case REQ_OVL_MODE:
2379 cur->overlay = 1;
2380 break;
2381
2382 case REQ_INS_MODE:
2383 cur->overlay = 0;
2384 break;
2385
2386 case REQ_SCR_FLINE:
2387 _formi_scroll_fwd(cur, 1);
2388 break;
2389
2390 case REQ_SCR_BLINE:
2391 _formi_scroll_back(cur, 1);
2392 break;
2393
2394 case REQ_SCR_FPAGE:
2395 _formi_scroll_fwd(cur, cur->rows);
2396 break;
2397
2398 case REQ_SCR_BPAGE:
2399 _formi_scroll_back(cur, cur->rows);
2400 break;
2401
2402 case REQ_SCR_FHPAGE:
2403 _formi_scroll_fwd(cur, cur->rows / 2);
2404 break;
2405
2406 case REQ_SCR_BHPAGE:
2407 _formi_scroll_back(cur, cur->rows / 2);
2408 break;
2409
2410 case REQ_SCR_FCHAR:
2411 _formi_hscroll_fwd(cur, 1);
2412 break;
2413
2414 case REQ_SCR_BCHAR:
2415 _formi_hscroll_back(cur, 1);
2416 break;
2417
2418 case REQ_SCR_HFLINE:
2419 _formi_hscroll_fwd(cur, cur->cols);
2420 break;
2421
2422 case REQ_SCR_HBLINE:
2423 _formi_hscroll_back(cur, cur->cols);
2424 break;
2425
2426 case REQ_SCR_HFHALF:
2427 _formi_hscroll_fwd(cur, cur->cols / 2);
2428 break;
2429
2430 case REQ_SCR_HBHALF:
2431 _formi_hscroll_back(cur, cur->cols / 2);
2432 break;
2433
2434 default:
2435 return 0;
2436 }
2437
2438 #ifdef DEBUG
2439 fprintf(dbg,
2440 "exit: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
2441 cur->cursor_xpos, cur->row_xpos, cur->start_char,
2442 cur->buffers[0].length, cur->buffers[0].allocated);
2443 fprintf(dbg, "exit: start_line=%d, ypos=%d\n", cur->start_line,
2444 cur->cursor_ypos);
2445 fprintf(dbg, "exit: string=\"%s\"\n", cur->buffers[0].string);
2446 assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX)
2447 && (cur->cursor_xpos >= cur->row_xpos));
2448 #endif
2449 return 1;
2450 }
2451
2452 /*
2453 * Validate the give character by passing it to any type character
2454 * checking routines, if they exist.
2455 */
2456 int
2457 _formi_validate_char(FIELD *field, char c)
2458 {
2459 int ret_val;
2460
2461 if (field->type == NULL)
2462 return E_OK;
2463
2464 ret_val = E_INVALID_FIELD;
2465 _formi_do_char_validation(field, field->type, c, &ret_val);
2466
2467 return ret_val;
2468 }
2469
2470
2471 /*
2472 * Perform the validation of the character, invoke all field_type validation
2473 * routines. If the field is ok then update ret_val to E_OK otherwise
2474 * ret_val is not changed.
2475 */
2476 static void
2477 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
2478 {
2479 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2480 _formi_do_char_validation(field, type->link->next, c, ret_val);
2481 _formi_do_char_validation(field, type->link->prev, c, ret_val);
2482 } else {
2483 if (type->char_check == NULL)
2484 *ret_val = E_OK;
2485 else {
2486 if (type->char_check((int)(unsigned char) c,
2487 field->args) == TRUE)
2488 *ret_val = E_OK;
2489 }
2490 }
2491 }
2492
2493 /*
2494 * Validate the current field. If the field validation returns success then
2495 * return E_OK otherwise return E_INVALID_FIELD.
2496 *
2497 */
2498 int
2499 _formi_validate_field(FORM *form)
2500 {
2501 FIELD *cur;
2502 char *bp;
2503 int ret_val, count;
2504
2505
2506 if ((form == NULL) || (form->fields == NULL) ||
2507 (form->fields[0] == NULL))
2508 return E_INVALID_FIELD;
2509
2510 cur = form->fields[form->cur_field];
2511
2512 bp = cur->buffers[0].string;
2513 count = _formi_skip_blanks(bp, 0);
2514
2515 /* check if we have a null field, depending on the nullok flag
2516 * this may be acceptable or not....
2517 */
2518 if (cur->buffers[0].string[count] == '\0') {
2519 if ((cur->opts & O_NULLOK) == O_NULLOK)
2520 return E_OK;
2521 else
2522 return E_INVALID_FIELD;
2523 }
2524
2525 /* check if an unmodified field is ok */
2526 if (cur->buf0_status == 0) {
2527 if ((cur->opts & O_PASSOK) == O_PASSOK)
2528 return E_OK;
2529 else
2530 return E_INVALID_FIELD;
2531 }
2532
2533 /* if there is no type then just accept the field */
2534 if (cur->type == NULL)
2535 return E_OK;
2536
2537 ret_val = E_INVALID_FIELD;
2538 _formi_do_validation(cur, cur->type, &ret_val);
2539
2540 return ret_val;
2541 }
2542
2543 /*
2544 * Perform the validation of the field, invoke all field_type validation
2545 * routines. If the field is ok then update ret_val to E_OK otherwise
2546 * ret_val is not changed.
2547 */
2548 static void
2549 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
2550 {
2551 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2552 _formi_do_validation(field, type->link->next, ret_val);
2553 _formi_do_validation(field, type->link->prev, ret_val);
2554 } else {
2555 if (type->field_check == NULL)
2556 *ret_val = E_OK;
2557 else {
2558 if (type->field_check(field, field_buffer(field, 0))
2559 == TRUE)
2560 *ret_val = E_OK;
2561 }
2562 }
2563 }
2564
2565 /*
2566 * Select the next/previous choice for the field, the driver command
2567 * selecting the direction will be passed in c. Return 1 if a choice
2568 * selection succeeded, 0 otherwise.
2569 */
2570 int
2571 _formi_field_choice(FORM *form, int c)
2572 {
2573 FIELDTYPE *type;
2574 FIELD *field;
2575
2576 if ((form == NULL) || (form->fields == NULL) ||
2577 (form->fields[0] == NULL) ||
2578 (form->fields[form->cur_field]->type == NULL))
2579 return 0;
2580
2581 field = form->fields[form->cur_field];
2582 type = field->type;
2583
2584 switch (c) {
2585 case REQ_NEXT_CHOICE:
2586 if (type->next_choice == NULL)
2587 return 0;
2588 else
2589 return type->next_choice(field,
2590 field_buffer(field, 0));
2591
2592 case REQ_PREV_CHOICE:
2593 if (type->prev_choice == NULL)
2594 return 0;
2595 else
2596 return type->prev_choice(field,
2597 field_buffer(field, 0));
2598
2599 default: /* should never happen! */
2600 return 0;
2601 }
2602 }
2603
2604 /*
2605 * Update the fields if they have changed. The parameter old has the
2606 * previous current field as the current field may have been updated by
2607 * the driver. Return 1 if the form page needs updating.
2608 *
2609 */
2610 int
2611 _formi_update_field(FORM *form, int old_field)
2612 {
2613 int cur, i;
2614
2615 cur = form->cur_field;
2616
2617 if (old_field != cur) {
2618 if (!((cur >= form->page_starts[form->page].first) &&
2619 (cur <= form->page_starts[form->page].last))) {
2620 /* not on same page any more */
2621 for (i = 0; i < form->max_page; i++) {
2622 if ((form->page_starts[i].in_use == 1) &&
2623 (form->page_starts[i].first <= cur) &&
2624 (form->page_starts[i].last >= cur)) {
2625 form->page = i;
2626 return 1;
2627 }
2628 }
2629 }
2630 }
2631
2632 _formi_redraw_field(form, old_field);
2633 _formi_redraw_field(form, form->cur_field);
2634 return 0;
2635 }
2636
2637 /*
2638 * Compare function for the field sorting
2639 *
2640 */
2641 static int
2642 field_sort_compare(const void *one, const void *two)
2643 {
2644 const FIELD *a, *b;
2645 int tl;
2646
2647 /* LINTED const castaway; we don't modify these! */
2648 a = (const FIELD *) *((const FIELD **) one);
2649 b = (const FIELD *) *((const FIELD **) two);
2650
2651 if (a == NULL)
2652 return 1;
2653
2654 if (b == NULL)
2655 return -1;
2656
2657 /*
2658 * First check the page, we want the fields sorted by page.
2659 *
2660 */
2661 if (a->page != b->page)
2662 return ((a->page > b->page)? 1 : -1);
2663
2664 tl = _formi_top_left(a->parent, a->index, b->index);
2665
2666 /*
2667 * sort fields left to right, top to bottom so the top left is
2668 * the less than value....
2669 */
2670 return ((tl == a->index)? -1 : 1);
2671 }
2672
2673 /*
2674 * Sort the fields in a form ready for driver traversal.
2675 */
2676 void
2677 _formi_sort_fields(FORM *form)
2678 {
2679 FIELD **sort_area;
2680 int i;
2681
2682 CIRCLEQ_INIT(&form->sorted_fields);
2683
2684 if ((sort_area = (FIELD **) malloc(sizeof(FIELD *) * form->field_count))
2685 == NULL)
2686 return;
2687
2688 bcopy(form->fields, sort_area, sizeof(FIELD *) * form->field_count);
2689 qsort(sort_area, (unsigned) form->field_count, sizeof(FIELD *),
2690 field_sort_compare);
2691
2692 for (i = 0; i < form->field_count; i++)
2693 CIRCLEQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
2694
2695 free(sort_area);
2696 }
2697
2698 /*
2699 * Set the neighbours for all the fields in the given form.
2700 */
2701 void
2702 _formi_stitch_fields(FORM *form)
2703 {
2704 int above_row, below_row, end_above, end_below, cur_row, real_end;
2705 FIELD *cur, *above, *below;
2706
2707 /*
2708 * check if the sorted fields circle queue is empty, just
2709 * return if it is.
2710 */
2711 if (CIRCLEQ_EMPTY(&form->sorted_fields))
2712 return;
2713
2714 /* initially nothing is above..... */
2715 above_row = -1;
2716 end_above = TRUE;
2717 above = NULL;
2718
2719 /* set up the first field as the current... */
2720 cur = CIRCLEQ_FIRST(&form->sorted_fields);
2721 cur_row = cur->form_row;
2722
2723 /* find the first field on the next row if any */
2724 below = CIRCLEQ_NEXT(cur, glue);
2725 below_row = -1;
2726 end_below = TRUE;
2727 real_end = TRUE;
2728 while (below != (void *)&form->sorted_fields) {
2729 if (below->form_row != cur_row) {
2730 below_row = below->form_row;
2731 end_below = FALSE;
2732 real_end = FALSE;
2733 break;
2734 }
2735 below = CIRCLEQ_NEXT(below, glue);
2736 }
2737
2738 /* walk the sorted fields, setting the neighbour pointers */
2739 while (cur != (void *) &form->sorted_fields) {
2740 if (cur == CIRCLEQ_FIRST(&form->sorted_fields))
2741 cur->left = NULL;
2742 else
2743 cur->left = CIRCLEQ_PREV(cur, glue);
2744
2745 if (cur == CIRCLEQ_LAST(&form->sorted_fields))
2746 cur->right = NULL;
2747 else
2748 cur->right = CIRCLEQ_NEXT(cur, glue);
2749
2750 if (end_above == TRUE)
2751 cur->up = NULL;
2752 else {
2753 cur->up = above;
2754 above = CIRCLEQ_NEXT(above, glue);
2755 if (above_row != above->form_row) {
2756 end_above = TRUE;
2757 above_row = above->form_row;
2758 }
2759 }
2760
2761 if (end_below == TRUE)
2762 cur->down = NULL;
2763 else {
2764 cur->down = below;
2765 below = CIRCLEQ_NEXT(below, glue);
2766 if (below == (void *) &form->sorted_fields) {
2767 end_below = TRUE;
2768 real_end = TRUE;
2769 } else if (below_row != below->form_row) {
2770 end_below = TRUE;
2771 below_row = below->form_row;
2772 }
2773 }
2774
2775 cur = CIRCLEQ_NEXT(cur, glue);
2776 if ((cur != (void *) &form->sorted_fields)
2777 && (cur_row != cur->form_row)) {
2778 cur_row = cur->form_row;
2779 if (end_above == FALSE) {
2780 for (; above != CIRCLEQ_FIRST(&form->sorted_fields);
2781 above = CIRCLEQ_NEXT(above, glue)) {
2782 if (above->form_row != above_row) {
2783 above_row = above->form_row;
2784 break;
2785 }
2786 }
2787 } else if (above == NULL) {
2788 above = CIRCLEQ_FIRST(&form->sorted_fields);
2789 end_above = FALSE;
2790 above_row = above->form_row;
2791 } else
2792 end_above = FALSE;
2793
2794 if (end_below == FALSE) {
2795 while (below_row == below->form_row) {
2796 below = CIRCLEQ_NEXT(below,
2797 glue);
2798 if (below ==
2799 (void *)&form->sorted_fields) {
2800 real_end = TRUE;
2801 end_below = TRUE;
2802 break;
2803 }
2804 }
2805
2806 if (below != (void *)&form->sorted_fields)
2807 below_row = below->form_row;
2808 } else if (real_end == FALSE)
2809 end_below = FALSE;
2810
2811 }
2812 }
2813 }
2814
2815 /*
2816 * Calculate the length of the displayed line allowing for any tab
2817 * characters that need to be expanded. We assume that the tab stops
2818 * are 8 characters apart. The parameters start and end are the
2819 * character positions in the string str we want to get the length of,
2820 * the function returns the number of characters from the start
2821 * position to the end position that should be displayed after any
2822 * intervening tabs have been expanded.
2823 */
2824 int
2825 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end)
2826 {
2827 int len, start_len, i;
2828
2829 /* if we have a null string then there is no length */
2830 if (str[0] == '\0')
2831 return 0;
2832
2833 len = 0;
2834 start_len = 0;
2835
2836 /*
2837 * preceding tabs affect the length tabs in the span, so
2838 * we need to calculate the length including the stuff before
2839 * start and then subtract off the unwanted bit.
2840 */
2841 for (i = 0; i <= end; i++) {
2842 if (i == start) /* stash preamble length for later */
2843 start_len = len;
2844
2845 if (str[i] == '\0')
2846 break;
2847
2848 if (str[i] == '\t')
2849 len = len - (len % 8) + 8;
2850 else
2851 len++;
2852 }
2853
2854 #ifdef DEBUG
2855 if (dbg != NULL) {
2856 fprintf(dbg,
2857 "tab_expanded: start=%d, end=%d, expanded=%d (diff=%d)\n",
2858 start, end, (len - start_len), (end - start));
2859 }
2860 #endif
2861
2862 return (len - start_len);
2863 }
2864
2865 /*
2866 * Calculate the tab stops on a given line in the field and set up
2867 * the tabs list with the results. We do this by scanning the line for tab
2868 * characters and if one is found, noting the position and the number of
2869 * characters to get to the next tab stop. This information is kept to
2870 * make manipulating the field (scrolling and so on) easier to handle.
2871 */
2872 void
2873 _formi_calculate_tabs(FIELD *field, unsigned row)
2874 {
2875 _formi_tab_t *ts = field->lines[row].tabs, *old_ts = NULL, **tsp;
2876 int i, j;
2877
2878 /*
2879 * If the line already has tabs then invalidate them by
2880 * walking the list and killing the in_use flag.
2881 */
2882 for (; ts != NULL; ts = ts->fwd)
2883 ts->in_use = FALSE;
2884
2885
2886 /*
2887 * Now look for tabs in the row and record the info...
2888 */
2889 tsp = &field->lines[row].tabs;
2890 for (i = field->lines[row].start, j = 0; i <= field->lines[row].end;
2891 i++, j++) {
2892 if (field->buffers[0].string[i] == '\t') {
2893 if (*tsp == NULL) {
2894 if ((*tsp = (_formi_tab_t *)
2895 malloc(sizeof(_formi_tab_t))) == NULL)
2896 return;
2897 (*tsp)->back = old_ts;
2898 (*tsp)->fwd = NULL;
2899 }
2900
2901 (*tsp)->in_use = TRUE;
2902 (*tsp)->pos = i - field->lines[row].start;
2903 (*tsp)->size = 8 - (j % 8);
2904 j += (*tsp)->size - 1;
2905 old_ts = *tsp;
2906 tsp = &(*tsp)->fwd;
2907 }
2908 }
2909 }
2910
2911 /*
2912 * Return the size of the tab padding for a tab character at the given
2913 * position. Return 1 if there is not a tab char entry matching the
2914 * given location.
2915 */
2916 static int
2917 tab_size(FIELD *field, unsigned int offset, unsigned int i)
2918 {
2919 int row;
2920 _formi_tab_t *ts;
2921
2922 row = find_cur_line(field, offset + i);
2923 ts = field->lines[row].tabs;
2924
2925 while ((ts != NULL) && (ts->pos != i))
2926 ts = ts->fwd;
2927
2928 if (ts == NULL)
2929 return 1;
2930 else
2931 return ts->size;
2932 }
2933
2934 /*
2935 * Find the character offset that corresponds to longest tab expanded
2936 * string that will fit into the given window. Walk the string backwards
2937 * evaluating the sizes of any tabs that are in the string. Note that
2938 * using this function on a multi-line window will produce undefined
2939 * results - it is really only required for a single row field.
2940 */
2941 static int
2942 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window)
2943 {
2944 int scroll_amt, i;
2945 _formi_tab_t *ts;
2946
2947 /* first find the last tab */
2948 ts = field->lines[0].tabs;
2949
2950 /*
2951 * unless there are no tabs - just return the window size,
2952 * if there is enough room, otherwise 0.
2953 */
2954 if (ts == NULL) {
2955 if (field->buffers[0].length < window)
2956 return 0;
2957 else
2958 return field->buffers[0].length - window + 1;
2959 }
2960
2961 while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE))
2962 ts = ts->fwd;
2963
2964 /*
2965 * now walk backwards finding the first tab that is to the
2966 * left of our starting pos.
2967 */
2968 while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos))
2969 ts = ts->back;
2970
2971 scroll_amt = 0;
2972 for (i = pos; i >= 0; i--) {
2973 if (field->buffers[0].string[i] == '\t') {
2974 #ifdef DEBUG
2975 assert((ts != NULL) && (ts->in_use == TRUE));
2976 #endif
2977 if (ts->pos == i) {
2978 if ((scroll_amt + ts->size) > window) {
2979 break;
2980 }
2981 scroll_amt += ts->size;
2982 ts = ts->back;
2983 }
2984 #ifdef DEBUG
2985 else
2986 assert(ts->pos == i);
2987 #endif
2988 } else {
2989 scroll_amt++;
2990 if (scroll_amt > window)
2991 break;
2992 }
2993 }
2994
2995 return ++i;
2996 }
2997
2998 /*
2999 * Return the position of the last character that will fit into the
3000 * given width after tabs have been expanded for a given row of a given
3001 * field.
3002 */
3003 static unsigned int
3004 tab_fit_len(FIELD *field, unsigned int row, unsigned int width)
3005 {
3006 unsigned int pos, len, row_pos;
3007 _formi_tab_t *ts;
3008
3009 ts = field->lines[row].tabs;
3010 pos = field->lines[row].start;
3011 len = 0;
3012 row_pos = 0;
3013
3014 while ((len < width) && (pos < field->buffers[0].length)) {
3015 if (field->buffers[0].string[pos] == '\t') {
3016 #ifdef DEBUG
3017 assert((ts != NULL) && (ts->in_use == TRUE));
3018 #endif
3019 if (ts->pos == row_pos) {
3020 if ((len + ts->size) > width)
3021 break;
3022 len += ts->size;
3023 ts = ts->fwd;
3024 }
3025 #ifdef DEBUG
3026 else
3027 assert(ts->pos == row_pos);
3028 #endif
3029 } else
3030 len++;
3031 pos++;
3032 row_pos++;
3033 }
3034
3035 if (pos > 0)
3036 pos--;
3037 return pos;
3038 }
3039