internals.c revision 1.27 1 /* $NetBSD: internals.c,v 1.27 2002/08/02 11:49:18 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->cols - 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 (c == REQ_RIGHT_CHAR))
1739 return E_REQUEST_DENIED;
1740
1741 cur->cursor_xpos = 0;
1742 cur->row_xpos = 0;
1743 if (cur->cursor_ypos == (cur->rows - 1))
1744 cur->start_line++;
1745 else
1746 cur->cursor_ypos++;
1747 } else {
1748 old_xpos = cur->cursor_xpos;
1749 old_row_pos = cur->row_xpos;
1750 if (saved == '\t')
1751 cur->cursor_xpos += tab_size(cur,
1752 cur->lines[row].start,
1753 cur->row_xpos);
1754 else
1755 cur->cursor_xpos++;
1756 cur->row_xpos++;
1757 if (cur->cursor_xpos
1758 >= cur->lines[row].length) {
1759 if (((row + 1) >= cur->row_count) ||
1760 (c == REQ_RIGHT_CHAR)) {
1761 cur->cursor_xpos = old_xpos;
1762 cur->row_xpos = old_row_pos;
1763 return E_REQUEST_DENIED;
1764 }
1765
1766 cur->cursor_xpos = 0;
1767 cur->row_xpos = 0;
1768 if (cur->cursor_ypos
1769 == (cur->rows - 1))
1770 cur->start_line++;
1771 else
1772 cur->cursor_ypos++;
1773 }
1774 }
1775 }
1776
1777 break;
1778
1779 case REQ_LEFT_CHAR:
1780 /*
1781 * The behaviour of left_char is the same as prev_char
1782 * except that the cursor will not wrap if it has
1783 * reached the LHS of the field, so just check this
1784 * and fall through if we are not at the LHS.
1785 */
1786 if (cur->cursor_xpos == 0)
1787 return E_REQUEST_DENIED;
1788
1789 /* FALLTHRU */
1790 case REQ_PREV_CHAR:
1791 if ((cur->rows + cur->nrows) == 1) {
1792 if (cur->row_xpos == 0) {
1793 if (cur->start_char > 0)
1794 cur->start_char--;
1795 else
1796 return E_REQUEST_DENIED;
1797 } else {
1798 cur->row_xpos--;
1799 _formi_set_cursor_xpos(cur);
1800 }
1801 } else {
1802 if ((cur->cursor_xpos == 0) &&
1803 (cur->cursor_ypos == 0) &&
1804 (cur->start_line == 0))
1805 return E_REQUEST_DENIED;
1806
1807 row = cur->start_line + cur->cursor_ypos;
1808 pos = cur->lines[row].start + cur->row_xpos;
1809 if ((pos >= cur->buffers[0].length) && (pos > 0))
1810 pos--;
1811
1812 if (cur->cursor_xpos > 0) {
1813 if (cur->buffers[0].string[pos] == '\t') {
1814 size = tab_size(cur,
1815 cur->lines[row].start,
1816 pos
1817 - cur->lines[row].start);
1818 if (size > cur->cursor_xpos) {
1819 cur->cursor_xpos = 0;
1820 cur->row_xpos = 0;
1821 } else {
1822 cur->row_xpos--;
1823 cur->cursor_xpos -= size;
1824 }
1825 } else {
1826 cur->cursor_xpos--;
1827 cur->row_xpos--;
1828 }
1829 } else {
1830 if (cur->cursor_ypos > 0)
1831 cur->cursor_ypos--;
1832 else
1833 cur->start_line--;
1834 row = cur->start_line + cur->cursor_ypos;
1835 cur->cursor_xpos = cur->lines[row].length - 1;
1836 cur->row_xpos = cur->lines[row].end -
1837 cur->lines[row].start;
1838 }
1839 }
1840
1841 break;
1842
1843 case REQ_DOWN_CHAR:
1844 /*
1845 * The down_char request has the same functionality as
1846 * the next_line request excepting that the field is not
1847 * scrolled if the cursor is at the bottom of the field.
1848 * Check to see if the cursor is at the bottom of the field
1849 * and if it is then deny the request otherwise fall
1850 * through to the next_line handler.
1851 */
1852 if (cur->cursor_ypos >= cur->rows - 1)
1853 return E_REQUEST_DENIED;
1854
1855 /* FALLTHRU */
1856
1857 case REQ_NEXT_LINE:
1858 if ((cur->start_line + cur->cursor_ypos + 1) >= cur->row_count)
1859 return E_REQUEST_DENIED;
1860
1861 if ((cur->cursor_ypos + 1) >= cur->rows) {
1862 cur->start_line++;
1863 } else
1864 cur->cursor_ypos++;
1865 row = cur->cursor_ypos + cur->start_line;
1866 cur->row_xpos = tab_fit_len(cur, row, cur->cursor_xpos)
1867 - cur->lines[row].start;
1868 cur->cursor_xpos =
1869 _formi_tab_expanded_length(
1870 &cur->buffers[0].string[cur->lines[row].start],
1871 0, cur->row_xpos);
1872 break;
1873
1874 case REQ_UP_CHAR:
1875 /*
1876 * The up_char request has the same functionality as
1877 * the prev_line request excepting the field is not
1878 * scrolled, check if the cursor is at the top of the
1879 * field, if it is deny the request otherwise fall
1880 * through to the prev_line handler.
1881 */
1882 if (cur->cursor_ypos == 0)
1883 return E_REQUEST_DENIED;
1884
1885 /* FALLTHRU */
1886
1887 case REQ_PREV_LINE:
1888 if (cur->cursor_ypos == 0) {
1889 if (cur->start_line == 0)
1890 return E_REQUEST_DENIED;
1891 cur->start_line--;
1892 } else
1893 cur->cursor_ypos--;
1894 row = cur->cursor_ypos + cur->start_line;
1895 cur->row_xpos = tab_fit_len(cur, row, cur->cursor_xpos + 1)
1896 - cur->lines[row].start;
1897 cur->cursor_xpos =
1898 _formi_tab_expanded_length(
1899 &cur->buffers[0].string[cur->lines[row].start],
1900 0, cur->row_xpos) - 1;
1901 break;
1902
1903 case REQ_NEXT_WORD:
1904 start = cur->lines[cur->start_line + cur->cursor_ypos].start
1905 + cur->row_xpos + cur->start_char;
1906 str = cur->buffers[0].string;
1907
1908 start = find_eow(str, start);
1909
1910 /* check if we hit the end */
1911 if (str[start] == '\0')
1912 return E_REQUEST_DENIED;
1913
1914 /* otherwise we must have found the start of a word...*/
1915 if ((cur->rows + cur->nrows) == 1) {
1916 /* single line field */
1917 size = _formi_tab_expanded_length(str,
1918 cur->start_char, start);
1919 if (size < cur->cols) {
1920 cur->row_xpos = start - cur->start_char;
1921 } else {
1922 cur->start_char = start;
1923 cur->row_xpos = 0;
1924 }
1925 _formi_set_cursor_xpos(cur);
1926 } else {
1927 /* multiline field */
1928 row = find_cur_line(cur, start);
1929 cur->row_xpos = start - cur->lines[row].start;
1930 cur->cursor_xpos = _formi_tab_expanded_length(
1931 &str[cur->lines[row].start],
1932 0, cur->row_xpos) - 1;
1933 if (row != (cur->start_line + cur->cursor_ypos)) {
1934 if (cur->cursor_ypos == (cur->rows - 1)) {
1935 cur->start_line = row - cur->rows + 1;
1936 } else {
1937 cur->cursor_ypos = row
1938 - cur->start_line;
1939 }
1940 }
1941 }
1942 break;
1943
1944 case REQ_PREV_WORD:
1945 start = cur->start_char + cur->row_xpos
1946 + cur->lines[cur->start_line + cur->cursor_ypos].start;
1947 if (cur->start_char > 0)
1948 start--;
1949
1950 if (start == 0)
1951 return E_REQUEST_DENIED;
1952
1953 str = cur->buffers[0].string;
1954
1955 start = find_sow(str, start);
1956
1957 if ((cur->rows + cur->nrows) == 1) {
1958 /* single line field */
1959 size = _formi_tab_expanded_length(str,
1960 cur->start_char, start);
1961
1962 if (start > cur->start_char) {
1963 cur->row_xpos = start - cur->start_char;
1964 } else {
1965 cur->start_char = start;
1966 cur->row_xpos = 0;
1967 }
1968 _formi_set_cursor_xpos(cur);
1969 } else {
1970 /* multiline field */
1971 row = find_cur_line(cur, start);
1972 cur->row_xpos = start - cur->lines[row].start;
1973 cur->cursor_xpos = _formi_tab_expanded_length(
1974 &str[cur->lines[row].start], 0,
1975 cur->row_xpos) - 1;
1976 if (row != (cur->start_line + cur->cursor_ypos)) {
1977 if (cur->cursor_ypos == 0) {
1978 cur->start_line = row;
1979 } else {
1980 if (cur->start_line > row) {
1981 cur->start_line = row;
1982 cur->cursor_ypos = 0;
1983 } else {
1984 cur->cursor_ypos = row -
1985 cur->start_line;
1986 }
1987 }
1988 }
1989 }
1990
1991 break;
1992
1993 case REQ_BEG_FIELD:
1994 cur->start_char = 0;
1995 cur->start_line = 0;
1996 cur->row_xpos = 0;
1997 _formi_init_field_xpos(cur);
1998 cur->cursor_ypos = 0;
1999 break;
2000
2001 case REQ_BEG_LINE:
2002 cur->row_xpos = 0;
2003 _formi_init_field_xpos(cur);
2004 cur->start_char = 0;
2005 break;
2006
2007 case REQ_END_FIELD:
2008 if (cur->row_count > cur->rows) {
2009 cur->start_line = cur->row_count - cur->rows;
2010 cur->cursor_ypos = cur->rows - 1;
2011 } else {
2012 cur->start_line = 0;
2013 cur->cursor_ypos = cur->row_count - 1;
2014 }
2015
2016 /* we fall through here deliberately, we are on the
2017 * correct row, now we need to get to the end of the
2018 * line.
2019 */
2020 /* FALLTHRU */
2021
2022 case REQ_END_LINE:
2023 row = cur->start_line + cur->cursor_ypos;
2024 start = cur->lines[row].start;
2025 end = cur->lines[row].end;
2026
2027 if ((cur->rows + cur->nrows) == 1) {
2028 if (cur->lines[row].length > cur->cols - 1) {
2029 if ((cur->opts & O_STATIC) != O_STATIC) {
2030 cur->start_char = tab_fit_window(
2031 cur, end, cur->cols) + 1;
2032 cur->row_xpos = cur->buffers[0].length
2033 - cur->start_char;
2034 } else {
2035 cur->start_char = 0;
2036 cur->row_xpos = cur->cols - 1;
2037 }
2038 } else {
2039 cur->row_xpos = end + 1;
2040 cur->start_char = 0;
2041 }
2042 _formi_set_cursor_xpos(cur);
2043 } else {
2044 cur->row_xpos = end - start;
2045 cur->cursor_xpos = cur->lines[row].length - 1;
2046 if (row == (cur->row_count - 1)) {
2047 cur->row_xpos++;
2048 cur->cursor_xpos++;
2049 }
2050 }
2051 break;
2052
2053 case REQ_NEW_LINE:
2054 if ((status = split_line(cur,
2055 cur->start_char + cur->row_xpos)) != E_OK)
2056 return status;
2057 break;
2058
2059 case REQ_INS_CHAR:
2060 if ((status = _formi_add_char(cur, cur->start_char
2061 + cur->row_xpos,
2062 cur->pad)) != E_OK)
2063 return status;
2064 break;
2065
2066 case REQ_INS_LINE:
2067 start = cur->lines[cur->start_line + cur->cursor_ypos].start;
2068 if ((status = split_line(cur, start)) != E_OK)
2069 return status;
2070 break;
2071
2072 case REQ_DEL_CHAR:
2073 if (cur->buffers[0].length == 0)
2074 return E_REQUEST_DENIED;
2075
2076 row = cur->start_line + cur->cursor_ypos;
2077 start = cur->start_char + cur->row_xpos
2078 + cur->lines[row].start;
2079 end = cur->buffers[0].length;
2080 if (start >= cur->buffers[0].length)
2081 return E_REQUEST_DENIED;
2082
2083 if (start == cur->lines[row].end) {
2084 if ((cur->rows + cur->nrows) > 1) {
2085 /*
2086 * If we have more than one row, join the
2087 * next row to make things easier unless
2088 * we are at the end of the string, in
2089 * that case the join would fail but we
2090 * really want to delete the last char
2091 * in the field.
2092 */
2093 if ((cur->row_count > 1)
2094 && (start != (end - 1))) {
2095 if (_formi_join_line(cur,
2096 start,
2097 JOIN_NEXT_NW)
2098 != E_OK) {
2099 return E_REQUEST_DENIED;
2100 }
2101 }
2102 }
2103 }
2104
2105 saved = cur->buffers[0].string[start];
2106 bcopy(&cur->buffers[0].string[start + 1],
2107 &cur->buffers[0].string[start],
2108 (unsigned) end - start + 1);
2109 bump_lines(cur, _FORMI_USE_CURRENT, -1, TRUE);
2110 cur->buffers[0].length--;
2111
2112 /*
2113 * recalculate tabs for a single line field, multiline
2114 * fields will do this when the field is wrapped.
2115 */
2116 if ((cur->rows + cur->nrows) == 1)
2117 _formi_calculate_tabs(cur, 0);
2118 /*
2119 * if we are at the end of the string then back the
2120 * cursor pos up one to stick on the end of the line
2121 */
2122 if (start == cur->buffers[0].length) {
2123 if (cur->buffers[0].length > 1) {
2124 if ((cur->rows + cur->nrows) == 1) {
2125 pos = cur->row_xpos + cur->start_char;
2126 cur->start_char =
2127 tab_fit_window(
2128 cur,
2129 cur->start_char + cur->row_xpos,
2130 cur->cols);
2131 cur->row_xpos = pos - cur->start_char
2132 - 1;
2133 _formi_set_cursor_xpos(cur);
2134 } else {
2135 if (cur->row_xpos == 0) {
2136 if (cur->lines[row].start !=
2137 cur->buffers[0].length) {
2138 if (_formi_join_line(
2139 cur,
2140 cur->lines[row].start,
2141 JOIN_PREV_NW)
2142 != E_OK) {
2143 return E_REQUEST_DENIED;
2144 }
2145 } else {
2146 if (cur->row_count > 1)
2147 cur->row_count--;
2148 }
2149
2150 row = cur->start_line
2151 + cur->cursor_ypos;
2152 if (row > 0)
2153 row--;
2154 }
2155
2156 cur->row_xpos = start
2157 - cur->lines[row].start - 1;
2158 cur->cursor_xpos =
2159 _formi_tab_expanded_length(
2160 &cur->buffers[0].string[cur->lines[row].start],
2161 0, cur->row_xpos);
2162 if ((cur->cursor_xpos > 0)
2163 && (start != (cur->buffers[0].length - 1)))
2164 cur->cursor_xpos--;
2165 if (row >= cur->rows)
2166 cur->start_line = row
2167 - cur->cursor_ypos;
2168 else {
2169 cur->start_line = 0;
2170 cur->cursor_ypos = row;
2171 }
2172 }
2173
2174 start--;
2175 } else {
2176 start = 0;
2177 cur->row_xpos = 0;
2178 _formi_init_field_xpos(cur);
2179 }
2180 }
2181
2182 if ((cur->rows + cur->nrows) > 1) {
2183 if (_formi_wrap_field(cur, start) != E_OK) {
2184 bcopy(&cur->buffers[0].string[start],
2185 &cur->buffers[0].string[start + 1],
2186 (unsigned) end - start);
2187 cur->buffers[0].length++;
2188 cur->buffers[0].string[start] = saved;
2189 bump_lines(cur, _FORMI_USE_CURRENT, 1, TRUE);
2190 _formi_wrap_field(cur, start);
2191 return E_REQUEST_DENIED;
2192 }
2193 }
2194 break;
2195
2196 case REQ_DEL_PREV:
2197 if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
2198 && (cur->start_line == 0) && (cur->cursor_ypos == 0))
2199 return E_REQUEST_DENIED;
2200
2201 row = cur->start_line + cur->cursor_ypos;
2202 start = cur->row_xpos + cur->start_char
2203 + cur->lines[row].start;
2204 end = cur->buffers[0].length;
2205
2206 if ((cur->start_char + cur->row_xpos) == 0) {
2207 /*
2208 * Join this line to the next one, but only if
2209 * we are not at the end of the string because
2210 * in that case there are no characters to join.
2211 */
2212 if (cur->lines[row].start != end) {
2213 if (_formi_join_line(cur,
2214 cur->lines[row].start,
2215 JOIN_PREV_NW) != E_OK) {
2216 return E_REQUEST_DENIED;
2217 }
2218 } else {
2219 /* but we do want the row count decremented */
2220 if (cur->row_count > 1)
2221 cur->row_count--;
2222 }
2223 }
2224
2225 saved = cur->buffers[0].string[start - 1];
2226 bcopy(&cur->buffers[0].string[start],
2227 &cur->buffers[0].string[start - 1],
2228 (unsigned) end - start + 1);
2229 bump_lines(cur, (int) start - 1, -1, TRUE);
2230 cur->buffers[0].length--;
2231
2232 if ((cur->rows + cur->nrows) == 1) {
2233 _formi_calculate_tabs(cur, 0);
2234 pos = cur->row_xpos + cur->start_char;
2235 if (pos > 0)
2236 pos--;
2237 cur->start_char =
2238 tab_fit_window(cur,
2239 cur->start_char + cur->row_xpos,
2240 cur->cols);
2241 cur->row_xpos = pos - cur->start_char;
2242 _formi_set_cursor_xpos(cur);
2243 } else {
2244 pos = start - 1;
2245 if (pos >= cur->buffers[0].length)
2246 pos = cur->buffers[0].length - 1;
2247
2248 if ((_formi_wrap_field(cur, pos) != E_OK)) {
2249 bcopy(&cur->buffers[0].string[start - 1],
2250 &cur->buffers[0].string[start],
2251 (unsigned) end - start);
2252 cur->buffers[0].length++;
2253 cur->buffers[0].string[start - 1] = saved;
2254 bump_lines(cur, (int) start - 1, 1, TRUE);
2255 _formi_wrap_field(cur, pos);
2256 return E_REQUEST_DENIED;
2257 }
2258
2259 row = find_cur_line(cur, pos);
2260 cur->row_xpos = start - cur->lines[row].start - 1;
2261 cur->cursor_xpos = _formi_tab_expanded_length(
2262 &cur->buffers[0].string[cur->lines[row].start],
2263 0, cur->row_xpos);
2264 if ((cur->cursor_xpos > 0)
2265 && (pos != (cur->buffers[0].length - 1)))
2266 cur->cursor_xpos--;
2267
2268 if (row >= cur->rows)
2269 cur->start_line = row - cur->cursor_ypos;
2270 else {
2271 cur->start_line = 0;
2272 cur->cursor_ypos = row;
2273 }
2274 }
2275 break;
2276
2277 case REQ_DEL_LINE:
2278 row = cur->start_line + cur->cursor_ypos;
2279 start = cur->lines[row].start;
2280 end = cur->lines[row].end;
2281 bcopy(&cur->buffers[0].string[end + 1],
2282 &cur->buffers[0].string[start],
2283 (unsigned) cur->buffers[0].length - end + 1);
2284
2285 if (((cur->rows + cur->nrows) == 1) ||
2286 (cur->row_count == 1)) {
2287 /* single line case */
2288 cur->buffers[0].length = 0;
2289 cur->lines[0].end = cur->lines[0].length = 0;
2290 cur->row_xpos = 0;
2291 _formi_init_field_xpos(cur);
2292 cur->cursor_ypos = 0;
2293 } else {
2294 /* multiline field */
2295 old_count = cur->row_count;
2296 cur->row_count--;
2297 if (cur->row_count == 0)
2298 cur->row_count = 1;
2299
2300 if (cur->row_count > 1)
2301 bcopy(&cur->lines[row + 1],
2302 &cur->lines[row],
2303 (unsigned) (cur->row_count - row)
2304 * sizeof(struct _formi_field_lines));
2305
2306 cur->lines[row].start = start;
2307 len = start - end - 1; /* yes, this is negative */
2308
2309 if (row < (cur->row_count - 1))
2310 bump_lines(cur, (int) start, len, FALSE);
2311 else if (old_count == 1) {
2312 cur->lines[0].end = cur->lines[0].length = 0;
2313 cur->cursor_xpos = 0;
2314 cur->row_xpos = 0;
2315 cur->cursor_ypos = 0;
2316 } else if (cur->row_count == 1) {
2317 cur->lines[0].length = cur->buffers[0].length
2318 + len;
2319 cur->lines[0].end = cur->lines[0].length - 1;
2320 }
2321
2322 cur->buffers[0].length += len;
2323
2324 if (row > (cur->row_count - 1)) {
2325 row--;
2326 if (cur->cursor_ypos == 0) {
2327 if (cur->start_line > 0) {
2328 cur->start_line--;
2329 }
2330 } else {
2331 cur->cursor_ypos--;
2332 }
2333 }
2334
2335 if (old_count > 1) {
2336 if (cur->cursor_xpos > cur->lines[row].length) {
2337 cur->cursor_xpos =
2338 cur->lines[row].length - 1;
2339 cur->row_xpos = cur->lines[row].end;
2340 }
2341
2342 if (row >= cur->rows)
2343 cur->start_line = row
2344 - cur->cursor_ypos;
2345 else {
2346 cur->start_line = 0;
2347 cur->cursor_ypos = row;
2348 }
2349 }
2350 }
2351 break;
2352
2353 case REQ_DEL_WORD:
2354 start = cur->start_char + cur->row_xpos
2355 + cur->lines[row].start;
2356 str = cur->buffers[0].string;
2357
2358 end = find_eow(str, start);
2359
2360 /*
2361 * If not at the start of a word then find the start,
2362 * we cannot blindly call find_sow because this will
2363 * skip back a word if we are already at the start of
2364 * a word.
2365 */
2366 if ((start > 0)
2367 && !(isblank(str[start - 1]) && !isblank(str[start])))
2368 start = find_sow(cur->buffers[0].string, start);
2369 bcopy(&cur->buffers[0].string[end],
2370 &cur->buffers[0].string[start],
2371 (unsigned) cur->buffers[0].length - end + 1);
2372 len = end - start;
2373 cur->buffers[0].length -= len;
2374 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
2375
2376 if ((cur->rows + cur->nrows) > 1) {
2377 row = cur->start_line + cur->cursor_ypos;
2378 if ((row + 1) < cur->row_count) {
2379 /*
2380 * if not on the last row we need to
2381 * join on the next row so the line
2382 * will be re-wrapped.
2383 */
2384 _formi_join_line(cur, cur->lines[row].end,
2385 JOIN_NEXT_NW);
2386 }
2387 _formi_wrap_field(cur, start);
2388 row = find_cur_line(cur, start);
2389 cur->row_xpos = start - cur->lines[row].start;
2390 cur->cursor_xpos = _formi_tab_expanded_length(
2391 &cur->buffers[0].string[cur->lines[row].start],
2392 0, cur->row_xpos);
2393 if ((cur->cursor_xpos > 0)
2394 && (start != (cur->buffers[0].length - 1)))
2395 cur->cursor_xpos--;
2396 } else {
2397 _formi_calculate_tabs(cur, 0);
2398 cur->row_xpos = start - cur->start_char;
2399 if (cur->row_xpos > 0)
2400 cur->row_xpos--;
2401 _formi_set_cursor_xpos(cur);
2402 }
2403 break;
2404
2405 case REQ_CLR_EOL:
2406 row = cur->start_line + cur->cursor_ypos;
2407 start = cur->start_char + cur->cursor_xpos;
2408 end = cur->lines[row].end;
2409 len = end - start;
2410 bcopy(&cur->buffers[0].string[end + 1],
2411 &cur->buffers[0].string[start],
2412 cur->buffers[0].length - end + 1);
2413 cur->buffers[0].length -= len;
2414 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
2415
2416 if (cur->row_xpos > cur->lines[row].length) {
2417 cur->row_xpos = cur->lines[row].end;
2418 if (cur->rows + cur->nrows == 1)
2419 _formi_set_cursor_xpos(cur);
2420 else
2421 cur->cursor_xpos = cur->lines[row].length;
2422 }
2423 break;
2424
2425 case REQ_CLR_EOF:
2426 row = cur->start_line + cur->cursor_ypos;
2427 cur->buffers[0].string[cur->start_char
2428 + cur->cursor_xpos] = '\0';
2429 cur->buffers[0].length = strlen(cur->buffers[0].string);
2430 cur->lines[row].end = cur->buffers[0].length;
2431 cur->lines[row].length = cur->lines[row].end
2432 - cur->lines[row].start;
2433 break;
2434
2435 case REQ_CLR_FIELD:
2436 cur->buffers[0].string[0] = '\0';
2437 cur->buffers[0].length = 0;
2438 cur->row_count = 1;
2439 cur->start_line = 0;
2440 cur->cursor_ypos = 0;
2441 cur->row_xpos = 0;
2442 _formi_init_field_xpos(cur);
2443 cur->start_char = 0;
2444 cur->lines[0].start = 0;
2445 cur->lines[0].end = 0;
2446 cur->lines[0].length = 0;
2447 break;
2448
2449 case REQ_OVL_MODE:
2450 cur->overlay = 1;
2451 break;
2452
2453 case REQ_INS_MODE:
2454 cur->overlay = 0;
2455 break;
2456
2457 case REQ_SCR_FLINE:
2458 _formi_scroll_fwd(cur, 1);
2459 break;
2460
2461 case REQ_SCR_BLINE:
2462 _formi_scroll_back(cur, 1);
2463 break;
2464
2465 case REQ_SCR_FPAGE:
2466 _formi_scroll_fwd(cur, cur->rows);
2467 break;
2468
2469 case REQ_SCR_BPAGE:
2470 _formi_scroll_back(cur, cur->rows);
2471 break;
2472
2473 case REQ_SCR_FHPAGE:
2474 _formi_scroll_fwd(cur, cur->rows / 2);
2475 break;
2476
2477 case REQ_SCR_BHPAGE:
2478 _formi_scroll_back(cur, cur->rows / 2);
2479 break;
2480
2481 case REQ_SCR_FCHAR:
2482 _formi_hscroll_fwd(cur, 1);
2483 break;
2484
2485 case REQ_SCR_BCHAR:
2486 _formi_hscroll_back(cur, 1);
2487 break;
2488
2489 case REQ_SCR_HFLINE:
2490 _formi_hscroll_fwd(cur, cur->cols);
2491 break;
2492
2493 case REQ_SCR_HBLINE:
2494 _formi_hscroll_back(cur, cur->cols);
2495 break;
2496
2497 case REQ_SCR_HFHALF:
2498 _formi_hscroll_fwd(cur, cur->cols / 2);
2499 break;
2500
2501 case REQ_SCR_HBHALF:
2502 _formi_hscroll_back(cur, cur->cols / 2);
2503 break;
2504
2505 default:
2506 return 0;
2507 }
2508
2509 #ifdef DEBUG
2510 fprintf(dbg,
2511 "exit: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
2512 cur->cursor_xpos, cur->row_xpos, cur->start_char,
2513 cur->buffers[0].length, cur->buffers[0].allocated);
2514 fprintf(dbg, "exit: start_line=%d, ypos=%d\n", cur->start_line,
2515 cur->cursor_ypos);
2516 fprintf(dbg, "exit: string=\"%s\"\n", cur->buffers[0].string);
2517 assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX)
2518 && (cur->cursor_xpos >= cur->row_xpos));
2519 #endif
2520 return 1;
2521 }
2522
2523 /*
2524 * Validate the give character by passing it to any type character
2525 * checking routines, if they exist.
2526 */
2527 int
2528 _formi_validate_char(FIELD *field, char c)
2529 {
2530 int ret_val;
2531
2532 if (field->type == NULL)
2533 return E_OK;
2534
2535 ret_val = E_INVALID_FIELD;
2536 _formi_do_char_validation(field, field->type, c, &ret_val);
2537
2538 return ret_val;
2539 }
2540
2541
2542 /*
2543 * Perform the validation of the character, invoke all field_type validation
2544 * routines. If the field is ok then update ret_val to E_OK otherwise
2545 * ret_val is not changed.
2546 */
2547 static void
2548 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
2549 {
2550 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2551 _formi_do_char_validation(field, type->link->next, c, ret_val);
2552 _formi_do_char_validation(field, type->link->prev, c, ret_val);
2553 } else {
2554 if (type->char_check == NULL)
2555 *ret_val = E_OK;
2556 else {
2557 if (type->char_check((int)(unsigned char) c,
2558 field->args) == TRUE)
2559 *ret_val = E_OK;
2560 }
2561 }
2562 }
2563
2564 /*
2565 * Validate the current field. If the field validation returns success then
2566 * return E_OK otherwise return E_INVALID_FIELD.
2567 *
2568 */
2569 int
2570 _formi_validate_field(FORM *form)
2571 {
2572 FIELD *cur;
2573 char *bp;
2574 int ret_val, count;
2575
2576
2577 if ((form == NULL) || (form->fields == NULL) ||
2578 (form->fields[0] == NULL))
2579 return E_INVALID_FIELD;
2580
2581 cur = form->fields[form->cur_field];
2582
2583 bp = cur->buffers[0].string;
2584 count = _formi_skip_blanks(bp, 0);
2585
2586 /* check if we have a null field, depending on the nullok flag
2587 * this may be acceptable or not....
2588 */
2589 if (cur->buffers[0].string[count] == '\0') {
2590 if ((cur->opts & O_NULLOK) == O_NULLOK)
2591 return E_OK;
2592 else
2593 return E_INVALID_FIELD;
2594 }
2595
2596 /* check if an unmodified field is ok */
2597 if (cur->buf0_status == 0) {
2598 if ((cur->opts & O_PASSOK) == O_PASSOK)
2599 return E_OK;
2600 else
2601 return E_INVALID_FIELD;
2602 }
2603
2604 /* if there is no type then just accept the field */
2605 if (cur->type == NULL)
2606 return E_OK;
2607
2608 ret_val = E_INVALID_FIELD;
2609 _formi_do_validation(cur, cur->type, &ret_val);
2610
2611 return ret_val;
2612 }
2613
2614 /*
2615 * Perform the validation of the field, invoke all field_type validation
2616 * routines. If the field is ok then update ret_val to E_OK otherwise
2617 * ret_val is not changed.
2618 */
2619 static void
2620 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
2621 {
2622 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2623 _formi_do_validation(field, type->link->next, ret_val);
2624 _formi_do_validation(field, type->link->prev, ret_val);
2625 } else {
2626 if (type->field_check == NULL)
2627 *ret_val = E_OK;
2628 else {
2629 if (type->field_check(field, field_buffer(field, 0))
2630 == TRUE)
2631 *ret_val = E_OK;
2632 }
2633 }
2634 }
2635
2636 /*
2637 * Select the next/previous choice for the field, the driver command
2638 * selecting the direction will be passed in c. Return 1 if a choice
2639 * selection succeeded, 0 otherwise.
2640 */
2641 int
2642 _formi_field_choice(FORM *form, int c)
2643 {
2644 FIELDTYPE *type;
2645 FIELD *field;
2646
2647 if ((form == NULL) || (form->fields == NULL) ||
2648 (form->fields[0] == NULL) ||
2649 (form->fields[form->cur_field]->type == NULL))
2650 return 0;
2651
2652 field = form->fields[form->cur_field];
2653 type = field->type;
2654
2655 switch (c) {
2656 case REQ_NEXT_CHOICE:
2657 if (type->next_choice == NULL)
2658 return 0;
2659 else
2660 return type->next_choice(field,
2661 field_buffer(field, 0));
2662
2663 case REQ_PREV_CHOICE:
2664 if (type->prev_choice == NULL)
2665 return 0;
2666 else
2667 return type->prev_choice(field,
2668 field_buffer(field, 0));
2669
2670 default: /* should never happen! */
2671 return 0;
2672 }
2673 }
2674
2675 /*
2676 * Update the fields if they have changed. The parameter old has the
2677 * previous current field as the current field may have been updated by
2678 * the driver. Return 1 if the form page needs updating.
2679 *
2680 */
2681 int
2682 _formi_update_field(FORM *form, int old_field)
2683 {
2684 int cur, i;
2685
2686 cur = form->cur_field;
2687
2688 if (old_field != cur) {
2689 if (!((cur >= form->page_starts[form->page].first) &&
2690 (cur <= form->page_starts[form->page].last))) {
2691 /* not on same page any more */
2692 for (i = 0; i < form->max_page; i++) {
2693 if ((form->page_starts[i].in_use == 1) &&
2694 (form->page_starts[i].first <= cur) &&
2695 (form->page_starts[i].last >= cur)) {
2696 form->page = i;
2697 return 1;
2698 }
2699 }
2700 }
2701 }
2702
2703 _formi_redraw_field(form, old_field);
2704 _formi_redraw_field(form, form->cur_field);
2705 return 0;
2706 }
2707
2708 /*
2709 * Compare function for the field sorting
2710 *
2711 */
2712 static int
2713 field_sort_compare(const void *one, const void *two)
2714 {
2715 const FIELD *a, *b;
2716 int tl;
2717
2718 /* LINTED const castaway; we don't modify these! */
2719 a = (const FIELD *) *((const FIELD **) one);
2720 b = (const FIELD *) *((const FIELD **) two);
2721
2722 if (a == NULL)
2723 return 1;
2724
2725 if (b == NULL)
2726 return -1;
2727
2728 /*
2729 * First check the page, we want the fields sorted by page.
2730 *
2731 */
2732 if (a->page != b->page)
2733 return ((a->page > b->page)? 1 : -1);
2734
2735 tl = _formi_top_left(a->parent, a->index, b->index);
2736
2737 /*
2738 * sort fields left to right, top to bottom so the top left is
2739 * the less than value....
2740 */
2741 return ((tl == a->index)? -1 : 1);
2742 }
2743
2744 /*
2745 * Sort the fields in a form ready for driver traversal.
2746 */
2747 void
2748 _formi_sort_fields(FORM *form)
2749 {
2750 FIELD **sort_area;
2751 int i;
2752
2753 CIRCLEQ_INIT(&form->sorted_fields);
2754
2755 if ((sort_area = (FIELD **) malloc(sizeof(FIELD *) * form->field_count))
2756 == NULL)
2757 return;
2758
2759 bcopy(form->fields, sort_area, sizeof(FIELD *) * form->field_count);
2760 qsort(sort_area, (unsigned) form->field_count, sizeof(FIELD *),
2761 field_sort_compare);
2762
2763 for (i = 0; i < form->field_count; i++)
2764 CIRCLEQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
2765
2766 free(sort_area);
2767 }
2768
2769 /*
2770 * Set the neighbours for all the fields in the given form.
2771 */
2772 void
2773 _formi_stitch_fields(FORM *form)
2774 {
2775 int above_row, below_row, end_above, end_below, cur_row, real_end;
2776 FIELD *cur, *above, *below;
2777
2778 /*
2779 * check if the sorted fields circle queue is empty, just
2780 * return if it is.
2781 */
2782 if (CIRCLEQ_EMPTY(&form->sorted_fields))
2783 return;
2784
2785 /* initially nothing is above..... */
2786 above_row = -1;
2787 end_above = TRUE;
2788 above = NULL;
2789
2790 /* set up the first field as the current... */
2791 cur = CIRCLEQ_FIRST(&form->sorted_fields);
2792 cur_row = cur->form_row;
2793
2794 /* find the first field on the next row if any */
2795 below = CIRCLEQ_NEXT(cur, glue);
2796 below_row = -1;
2797 end_below = TRUE;
2798 real_end = TRUE;
2799 while (below != (void *)&form->sorted_fields) {
2800 if (below->form_row != cur_row) {
2801 below_row = below->form_row;
2802 end_below = FALSE;
2803 real_end = FALSE;
2804 break;
2805 }
2806 below = CIRCLEQ_NEXT(below, glue);
2807 }
2808
2809 /* walk the sorted fields, setting the neighbour pointers */
2810 while (cur != (void *) &form->sorted_fields) {
2811 if (cur == CIRCLEQ_FIRST(&form->sorted_fields))
2812 cur->left = NULL;
2813 else
2814 cur->left = CIRCLEQ_PREV(cur, glue);
2815
2816 if (cur == CIRCLEQ_LAST(&form->sorted_fields))
2817 cur->right = NULL;
2818 else
2819 cur->right = CIRCLEQ_NEXT(cur, glue);
2820
2821 if (end_above == TRUE)
2822 cur->up = NULL;
2823 else {
2824 cur->up = above;
2825 above = CIRCLEQ_NEXT(above, glue);
2826 if (above_row != above->form_row) {
2827 end_above = TRUE;
2828 above_row = above->form_row;
2829 }
2830 }
2831
2832 if (end_below == TRUE)
2833 cur->down = NULL;
2834 else {
2835 cur->down = below;
2836 below = CIRCLEQ_NEXT(below, glue);
2837 if (below == (void *) &form->sorted_fields) {
2838 end_below = TRUE;
2839 real_end = TRUE;
2840 } else if (below_row != below->form_row) {
2841 end_below = TRUE;
2842 below_row = below->form_row;
2843 }
2844 }
2845
2846 cur = CIRCLEQ_NEXT(cur, glue);
2847 if ((cur != (void *) &form->sorted_fields)
2848 && (cur_row != cur->form_row)) {
2849 cur_row = cur->form_row;
2850 if (end_above == FALSE) {
2851 for (; above != CIRCLEQ_FIRST(&form->sorted_fields);
2852 above = CIRCLEQ_NEXT(above, glue)) {
2853 if (above->form_row != above_row) {
2854 above_row = above->form_row;
2855 break;
2856 }
2857 }
2858 } else if (above == NULL) {
2859 above = CIRCLEQ_FIRST(&form->sorted_fields);
2860 end_above = FALSE;
2861 above_row = above->form_row;
2862 } else
2863 end_above = FALSE;
2864
2865 if (end_below == FALSE) {
2866 while (below_row == below->form_row) {
2867 below = CIRCLEQ_NEXT(below,
2868 glue);
2869 if (below ==
2870 (void *)&form->sorted_fields) {
2871 real_end = TRUE;
2872 end_below = TRUE;
2873 break;
2874 }
2875 }
2876
2877 if (below != (void *)&form->sorted_fields)
2878 below_row = below->form_row;
2879 } else if (real_end == FALSE)
2880 end_below = FALSE;
2881
2882 }
2883 }
2884 }
2885
2886 /*
2887 * Calculate the length of the displayed line allowing for any tab
2888 * characters that need to be expanded. We assume that the tab stops
2889 * are 8 characters apart. The parameters start and end are the
2890 * character positions in the string str we want to get the length of,
2891 * the function returns the number of characters from the start
2892 * position to the end position that should be displayed after any
2893 * intervening tabs have been expanded.
2894 */
2895 int
2896 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end)
2897 {
2898 int len, start_len, i;
2899
2900 /* if we have a null string then there is no length */
2901 if (str[0] == '\0')
2902 return 0;
2903
2904 len = 0;
2905 start_len = 0;
2906
2907 /*
2908 * preceding tabs affect the length tabs in the span, so
2909 * we need to calculate the length including the stuff before
2910 * start and then subtract off the unwanted bit.
2911 */
2912 for (i = 0; i <= end; i++) {
2913 if (i == start) /* stash preamble length for later */
2914 start_len = len;
2915
2916 if (str[i] == '\0')
2917 break;
2918
2919 if (str[i] == '\t')
2920 len = len - (len % 8) + 8;
2921 else
2922 len++;
2923 }
2924
2925 #ifdef DEBUG
2926 if (dbg != NULL) {
2927 fprintf(dbg,
2928 "tab_expanded: start=%d, end=%d, expanded=%d (diff=%d)\n",
2929 start, end, (len - start_len), (end - start));
2930 }
2931 #endif
2932
2933 return (len - start_len);
2934 }
2935
2936 /*
2937 * Calculate the tab stops on a given line in the field and set up
2938 * the tabs list with the results. We do this by scanning the line for tab
2939 * characters and if one is found, noting the position and the number of
2940 * characters to get to the next tab stop. This information is kept to
2941 * make manipulating the field (scrolling and so on) easier to handle.
2942 */
2943 void
2944 _formi_calculate_tabs(FIELD *field, unsigned row)
2945 {
2946 _formi_tab_t *ts = field->lines[row].tabs, *old_ts = NULL, **tsp;
2947 int i, j;
2948
2949 /*
2950 * If the line already has tabs then invalidate them by
2951 * walking the list and killing the in_use flag.
2952 */
2953 for (; ts != NULL; ts = ts->fwd)
2954 ts->in_use = FALSE;
2955
2956
2957 /*
2958 * Now look for tabs in the row and record the info...
2959 */
2960 tsp = &field->lines[row].tabs;
2961 for (i = field->lines[row].start, j = 0; i <= field->lines[row].end;
2962 i++, j++) {
2963 if (field->buffers[0].string[i] == '\t') {
2964 if (*tsp == NULL) {
2965 if ((*tsp = (_formi_tab_t *)
2966 malloc(sizeof(_formi_tab_t))) == NULL)
2967 return;
2968 (*tsp)->back = old_ts;
2969 (*tsp)->fwd = NULL;
2970 }
2971
2972 (*tsp)->in_use = TRUE;
2973 (*tsp)->pos = i - field->lines[row].start;
2974 (*tsp)->size = 8 - (j % 8);
2975 j += (*tsp)->size - 1;
2976 old_ts = *tsp;
2977 tsp = &(*tsp)->fwd;
2978 }
2979 }
2980 }
2981
2982 /*
2983 * Return the size of the tab padding for a tab character at the given
2984 * position. Return 1 if there is not a tab char entry matching the
2985 * given location.
2986 */
2987 static int
2988 tab_size(FIELD *field, unsigned int offset, unsigned int i)
2989 {
2990 int row;
2991 _formi_tab_t *ts;
2992
2993 row = find_cur_line(field, offset + i);
2994 ts = field->lines[row].tabs;
2995
2996 while ((ts != NULL) && (ts->pos != i))
2997 ts = ts->fwd;
2998
2999 if (ts == NULL)
3000 return 1;
3001 else
3002 return ts->size;
3003 }
3004
3005 /*
3006 * Find the character offset that corresponds to longest tab expanded
3007 * string that will fit into the given window. Walk the string backwards
3008 * evaluating the sizes of any tabs that are in the string. Note that
3009 * using this function on a multi-line window will produce undefined
3010 * results - it is really only required for a single row field.
3011 */
3012 static int
3013 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window)
3014 {
3015 int scroll_amt, i;
3016 _formi_tab_t *ts;
3017
3018 /* first find the last tab */
3019 ts = field->lines[0].tabs;
3020
3021 /*
3022 * unless there are no tabs - just return the window size,
3023 * if there is enough room, otherwise 0.
3024 */
3025 if (ts == NULL) {
3026 if (field->buffers[0].length < window)
3027 return 0;
3028 else
3029 return field->buffers[0].length - window + 1;
3030 }
3031
3032 while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE))
3033 ts = ts->fwd;
3034
3035 /*
3036 * now walk backwards finding the first tab that is to the
3037 * left of our starting pos.
3038 */
3039 while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos))
3040 ts = ts->back;
3041
3042 scroll_amt = 0;
3043 for (i = pos; i >= 0; i--) {
3044 if (field->buffers[0].string[i] == '\t') {
3045 #ifdef DEBUG
3046 assert((ts != NULL) && (ts->in_use == TRUE));
3047 #endif
3048 if (ts->pos == i) {
3049 if ((scroll_amt + ts->size) > window) {
3050 break;
3051 }
3052 scroll_amt += ts->size;
3053 ts = ts->back;
3054 }
3055 #ifdef DEBUG
3056 else
3057 assert(ts->pos == i);
3058 #endif
3059 } else {
3060 scroll_amt++;
3061 if (scroll_amt > window)
3062 break;
3063 }
3064 }
3065
3066 return ++i;
3067 }
3068
3069 /*
3070 * Return the position of the last character that will fit into the
3071 * given width after tabs have been expanded for a given row of a given
3072 * field.
3073 */
3074 static unsigned int
3075 tab_fit_len(FIELD *field, unsigned int row, unsigned int width)
3076 {
3077 unsigned int pos, len, row_pos;
3078 _formi_tab_t *ts;
3079
3080 ts = field->lines[row].tabs;
3081 pos = field->lines[row].start;
3082 len = 0;
3083 row_pos = 0;
3084
3085 while ((len < width) && (pos < field->buffers[0].length)) {
3086 if (field->buffers[0].string[pos] == '\t') {
3087 #ifdef DEBUG
3088 assert((ts != NULL) && (ts->in_use == TRUE));
3089 #endif
3090 if (ts->pos == row_pos) {
3091 if ((len + ts->size) > width)
3092 break;
3093 len += ts->size;
3094 ts = ts->fwd;
3095 }
3096 #ifdef DEBUG
3097 else
3098 assert(ts->pos == row_pos);
3099 #endif
3100 } else
3101 len++;
3102 pos++;
3103 row_pos++;
3104 }
3105
3106 if (pos > 0)
3107 pos--;
3108 return pos;
3109 }
3110