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