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