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