internals.c revision 1.20 1 /* $NetBSD: internals.c,v 1.20 2001/06/23 13:34:01 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 #endif
48
49 /* define our own min function - this is not generic but will do here
50 * (don't believe me? think about what value you would get
51 * from min(x++, y++)
52 */
53 #define min(a,b) (((a) > (b))? (b) : (a))
54
55 /* for the line joining function... */
56 #define JOIN_NEXT 1
57 #define JOIN_NEXT_NW 2 /* next join, don't wrap the joined line */
58 #define JOIN_PREV 3
59 #define JOIN_PREV_NW 4 /* previous join, don't wrap the joined line */
60
61 /* for the bump_lines function... */
62 #define _FORMI_USE_CURRENT -1 /* indicates current cursor pos to be used */
63
64 static void
65 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val);
66 static void
67 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val);
68 static int
69 _formi_join_line(FIELD *field, unsigned int pos, int direction);
70 void
71 _formi_hscroll_back(FIELD *field, unsigned int amt);
72 void
73 _formi_hscroll_fwd(FIELD *field, unsigned int amt);
74 static void
75 _formi_scroll_back(FIELD *field, unsigned int amt);
76 static void
77 _formi_scroll_fwd(FIELD *field, unsigned int amt);
78 static int
79 find_sow(char *str, unsigned int offset);
80 static int
81 find_cur_line(FIELD *cur, unsigned pos);
82 static int
83 split_line(FIELD *field, unsigned pos);
84 static void
85 bump_lines(FIELD *field, int pos, int amt, bool do_len);
86
87
88 /*
89 * Open the debug file if it is not already open....
90 */
91 #ifdef DEBUG
92 int
93 _formi_create_dbg_file(void)
94 {
95 if (dbg == NULL) {
96 dbg = fopen("___form_dbg.out", "w");
97 if (dbg == NULL) {
98 fprintf(stderr, "Cannot open debug file!\n");
99 return E_SYSTEM_ERROR;
100 }
101 }
102
103 return E_OK;
104 }
105 #endif
106
107 /*
108 * Bump the lines array elements in the given field by the given amount.
109 * The row to start acting on can either be inferred from the given position
110 * or if the special value _FORMI_USE_CURRENT is set then the row will be
111 * the row the cursor is currently on.
112 */
113 static void
114 bump_lines(FIELD *field, int pos, int amt, bool do_len)
115 {
116 int i, row;
117 #ifdef DEBUG
118 int dbg_ok = FALSE;
119 #endif
120
121 if (pos == _FORMI_USE_CURRENT)
122 row = field->start_line + field->cursor_ypos;
123 else
124 row = find_cur_line(field, (unsigned) pos);
125
126 #ifdef DEBUG
127 if (_formi_create_dbg_file() == E_OK) {
128 dbg_ok = TRUE;
129 fprintf(dbg, "bump_lines: bump starting at row %d\n", row);
130 fprintf(dbg,
131 "bump_lines: len from %d to %d, end from %d to %d\n",
132 field->lines[row].length,
133 field->lines[row].length + amt,
134 field->lines[row].end, field->lines[row].end + amt);
135 }
136 #endif
137
138 if (((int)field->lines[row].length + amt) < 0) {
139 field->lines[row].length = 0;
140 field->lines[row].end = 0;
141 } else {
142 if (do_len == TRUE)
143 field->lines[row].length += amt;
144 }
145
146 if (field->lines[row].length > 1)
147 field->lines[row].end += amt;
148 else
149 field->lines[row].end = field->lines[row].start;
150
151 for (i = row + 1; i < field->row_count; i++) {
152 #ifdef DEBUG
153 if (dbg_ok) {
154 fprintf(dbg,
155 "bump_lines: row %d: len from %d to %d, end from %d to %d\n",
156 i, field->lines[i].start,
157 field->lines[i].start + amt,
158 field->lines[i].end,
159 field->lines[i].end + amt);
160 }
161 fflush(dbg);
162 #endif
163 field->lines[i].start += amt;
164 field->lines[i].end += amt;
165 }
166 }
167
168 /*
169 * Set the form's current field to the first valid field on the page.
170 * Assume the fields have been sorted and stitched.
171 */
172 int
173 _formi_pos_first_field(FORM *form)
174 {
175 FIELD *cur;
176 int old_page;
177
178 old_page = form->page;
179
180 /* scan forward for an active page....*/
181 while (form->page_starts[form->page].in_use == 0) {
182 form->page++;
183 if (form->page > form->max_page) {
184 form->page = old_page;
185 return E_REQUEST_DENIED;
186 }
187 }
188
189 /* then scan for a field we can use */
190 cur = form->fields[form->page_starts[form->page].first];
191 while ((cur->opts & (O_VISIBLE | O_ACTIVE))
192 != (O_VISIBLE | O_ACTIVE)) {
193 cur = CIRCLEQ_NEXT(cur, glue);
194 if (cur == (void *) &form->sorted_fields) {
195 form->page = old_page;
196 return E_REQUEST_DENIED;
197 }
198 }
199
200 form->cur_field = cur->index;
201 return E_OK;
202 }
203
204 /*
205 * Set the field to the next active and visible field, the fields are
206 * traversed in index order in the direction given. If the parameter
207 * use_sorted is TRUE then the sorted field list will be traversed instead
208 * of using the field index.
209 */
210 int
211 _formi_pos_new_field(FORM *form, unsigned direction, unsigned use_sorted)
212 {
213 FIELD *cur;
214 int i;
215
216 i = form->cur_field;
217 cur = form->fields[i];
218
219 do {
220 if (direction == _FORMI_FORWARD) {
221 if (use_sorted == TRUE) {
222 if ((form->wrap == FALSE) &&
223 (cur == CIRCLEQ_LAST(&form->sorted_fields)))
224 return E_REQUEST_DENIED;
225 cur = CIRCLEQ_NEXT(cur, glue);
226 i = cur->index;
227 } else {
228 if ((form->wrap == FALSE) &&
229 ((i + 1) >= form->field_count))
230 return E_REQUEST_DENIED;
231 i++;
232 if (i >= form->field_count)
233 i = 0;
234 }
235 } else {
236 if (use_sorted == TRUE) {
237 if ((form->wrap == FALSE) &&
238 (cur == CIRCLEQ_FIRST(&form->sorted_fields)))
239 return E_REQUEST_DENIED;
240 cur = CIRCLEQ_PREV(cur, glue);
241 i = cur->index;
242 } else {
243 if ((form->wrap == FALSE) && (i <= 0))
244 return E_REQUEST_DENIED;
245 i--;
246 if (i < 0)
247 i = form->field_count - 1;
248 }
249 }
250
251 if ((form->fields[i]->opts & (O_VISIBLE | O_ACTIVE))
252 == (O_VISIBLE | O_ACTIVE)) {
253 form->cur_field = i;
254 return E_OK;
255 }
256 }
257 while (i != form->cur_field);
258
259 return E_REQUEST_DENIED;
260 }
261
262 /*
263 * Find the line in a field that the cursor is currently on.
264 */
265 static int
266 find_cur_line(FIELD *cur, unsigned pos)
267 {
268 unsigned row;
269
270 /* first check if pos is at the end of the string, if this
271 * is true then just return the last row since the pos may
272 * not have been added to the lines array yet.
273 */
274 if (pos == (cur->buffers[0].length - 1))
275 return (cur->row_count - 1);
276
277 for (row = 0; row < cur->row_count; row++) {
278 if ((pos >= cur->lines[row].start)
279 && (pos <= cur->lines[row].end))
280 return row;
281 }
282
283 #ifdef DEBUG
284 /* barf if we get here, this should not be possible */
285 assert((row != row));
286 #endif
287 return 0;
288 }
289
290
291 /*
292 * Word wrap the contents of the field's buffer 0 if this is allowed.
293 * If the wrap is successful, that is, the row count nor the buffer
294 * size is exceeded then the function will return E_OK, otherwise it
295 * will return E_REQUEST_DENIED.
296 */
297 int
298 _formi_wrap_field(FIELD *field, unsigned int loc)
299 {
300 char *str;
301 int width, row, start_row;
302 unsigned int pos;
303
304 str = field->buffers[0].string;
305
306 /* Don't bother if the field string is too short. */
307 if (field->buffers[0].length < field->cols)
308 return E_OK;
309
310 if ((field->opts & O_STATIC) == O_STATIC) {
311 if ((field->rows + field->nrows) == 1) {
312 return E_OK; /* cannot wrap a single line */
313 }
314 width = field->cols;
315 } else {
316 /* if we are limited to one line then don't try to wrap */
317 if ((field->drows + field->nrows) == 1) {
318 return E_OK;
319 }
320
321 /*
322 * hueristic - if a dynamic field has more than one line
323 * on the screen then the field grows rows, otherwise
324 * it grows columns, effectively a single line field.
325 * This is documented AT&T behaviour.
326 */
327 if (field->rows > 1) {
328 width = field->cols;
329 } else {
330 return E_OK;
331 }
332 }
333
334 start_row = find_cur_line(field, loc);
335
336 /* if we are not at the top of the field then back up one
337 * row because we may be able to merge the current row into
338 * the one above.
339 */
340 if (start_row > 0)
341 start_row--;
342
343 for (row = start_row; row < field->row_count; row++) {
344 AGAIN:
345 pos = field->lines[row].end;
346 if (field->lines[row].length < width) {
347 /* line may be too short, try joining some lines */
348
349 if ((((int) field->row_count) - 1) == row) {
350 /* if this is the last row then don't
351 * wrap
352 */
353 continue;
354 }
355
356 if (_formi_join_line(field, (unsigned int) pos,
357 JOIN_NEXT_NW) == E_OK) {
358 goto AGAIN;
359 } else
360 break;
361 } else {
362 /* line is too long, split it - maybe */
363
364 /* first check if we have not run out of room */
365 if ((field->opts & O_STATIC) == O_STATIC) {
366 /* check static field */
367 if ((field->rows + field->nrows - 1) == row)
368 return E_REQUEST_DENIED;
369 } else {
370 /* check dynamic field */
371 if ((field->max != 0)
372 && ((field->max - 1) == row))
373 return E_REQUEST_DENIED;
374 }
375
376 /* split on first whitespace before current word */
377 pos = width + field->lines[row].start - 1;
378 if (pos >= field->buffers[0].length)
379 pos = field->buffers[0].length - 1;
380
381 if ((!isblank(str[pos])) &&
382 ((field->opts & O_WRAP) == O_WRAP)) {
383 if (!isblank(str[pos - 1]))
384 pos = find_sow(str,
385 (unsigned int) pos);
386 /*
387 * If we cannot split the line then return
388 * NO_ROOM so the driver can tell that it
389 * should not autoskip (if that is enabled)
390 */
391 if ((pos == 0) || (!isblank(str[pos - 1]))
392 || ((pos <= field->lines[row].start)
393 && (field->buffers[0].length
394 >= (width - 1
395 + field->lines[row].start)))) {
396 return E_NO_ROOM;
397 }
398 }
399
400 /* if we are at the end of the string and it has
401 * a trailing blank, don't wrap the blank.
402 */
403 if ((pos == field->buffers[0].length - 1) &&
404 (isblank(str[pos])))
405 continue;
406
407 /*
408 * otherwise, if we are still sitting on a
409 * blank but not at the end of the line
410 * move forward one char so the blank
411 * is on the line boundary.
412 */
413 if (isblank(str[pos]))
414 pos++;
415
416 if (split_line(field, pos) != E_OK) {
417 return E_REQUEST_DENIED;
418 }
419 }
420 }
421
422 return E_OK;
423 }
424
425 /*
426 * Join the two lines that surround the location pos, the type
427 * variable indicates the direction of the join, JOIN_NEXT will join
428 * the next line to the current line, JOIN_PREV will join the current
429 * line to the previous line, the new lines will be wrapped unless the
430 * _NW versions of the directions are used. Returns E_OK if the join
431 * was successful or E_REQUEST_DENIED if the join cannot happen.
432 */
433 static int
434 _formi_join_line(FIELD *field, unsigned int pos, int direction)
435 {
436 unsigned int row, i;
437 int old_alloced, old_row_count;
438 struct _formi_field_lines *saved;
439 #ifdef DEBUG
440 int dbg_ok = FALSE;
441
442 if (_formi_create_dbg_file() == E_OK) {
443 dbg_ok = TRUE;
444 }
445 #endif
446
447 if ((saved = (struct _formi_field_lines *)
448 malloc(field->lines_alloced * sizeof(struct _formi_field_lines)))
449 == NULL)
450 return E_REQUEST_DENIED;
451
452 bcopy(field->lines, saved,
453 field->row_count * sizeof(struct _formi_field_lines));
454 old_alloced = field->lines_alloced;
455 old_row_count = field->row_count;
456
457 row = find_cur_line(field, pos);
458
459 #ifdef DEBUG
460 if (dbg_ok == TRUE) {
461 fprintf(dbg, "join_line: working on row %d, row_count = %d\n",
462 row, field->row_count);
463 }
464 #endif
465
466 if ((direction == JOIN_NEXT) || (direction == JOIN_NEXT_NW)) {
467 /* see if there is another line following... */
468 if (row == (field->row_count - 1)) {
469 free(saved);
470 return E_REQUEST_DENIED;
471 }
472
473 #ifdef DEBUG
474 if (dbg_ok == TRUE) {
475 fprintf(dbg,
476 "join_line: join_next before end = %d, length = %d",
477 field->lines[row].end,
478 field->lines[row].length);
479 fprintf(dbg,
480 " :: next row end = %d, length = %d\n",
481 field->lines[row + 1].end,
482 field->lines[row + 1].length);
483 }
484 #endif
485
486 field->lines[row].end = field->lines[row + 1].end;
487 field->lines[row].length += field->lines[row + 1].length;
488 /* shift all the remaining lines up.... */
489 for (i = row + 2; i < field->row_count; i++)
490 field->lines[i - 1] = field->lines[i];
491 } else {
492 if ((pos == 0) || (row == 0)) {
493 free(saved);
494 return E_REQUEST_DENIED;
495 }
496
497 #ifdef DEBUG
498 if (dbg_ok == TRUE) {
499 fprintf(dbg,
500 "join_line: join_prev before end = %d, length = %d",
501 field->lines[row].end,
502 field->lines[row].length);
503 fprintf(dbg,
504 " :: prev row end = %d, length = %d\n",
505 field->lines[row - 1].end,
506 field->lines[row - 1].length);
507 }
508 #endif
509
510 field->lines[row - 1].end = field->lines[row].end;
511 field->lines[row - 1].length += field->lines[row].length;
512 /* shift all the remaining lines up */
513 for (i = row + 1; i < field->row_count; i++)
514 field->lines[i - 1] = field->lines[i];
515 }
516
517 #ifdef DEBUG
518 if (dbg_ok == TRUE) {
519 fprintf(dbg,
520 "join_line: exit end = %d, length = %d\n",
521 field->lines[row].end, field->lines[row].length);
522 }
523 #endif
524
525 field->row_count--;
526
527 /* wrap the field if required, if this fails undo the change */
528 if ((direction == JOIN_NEXT) || (direction == JOIN_PREV)) {
529 if (_formi_wrap_field(field, (unsigned int) pos) != E_OK) {
530 free(field->lines);
531 field->lines = saved;
532 field->lines_alloced = old_alloced;
533 field->row_count = old_row_count;
534 return E_REQUEST_DENIED;
535 }
536 }
537
538 free(saved);
539 return E_OK;
540 }
541
542 /*
543 * Split the line at the given position, if possible
544 */
545 static int
546 split_line(FIELD *field, unsigned pos)
547 {
548 struct _formi_field_lines *new_lines;
549 unsigned int row, i;
550 #ifdef DEBUG
551 short dbg_ok = FALSE;
552 #endif
553
554 if (pos == 0)
555 return E_REQUEST_DENIED;
556
557 #ifdef DEBUG
558 if (_formi_create_dbg_file() == E_OK) {
559 fprintf(dbg, "split_line: splitting line at %d\n", pos);
560 dbg_ok = TRUE;
561 }
562 #endif
563
564 if ((field->row_count + 1) > field->lines_alloced) {
565 if ((new_lines = (struct _formi_field_lines *)
566 realloc(field->lines, (field->row_count + 1)
567 * sizeof(struct _formi_field_lines))) == NULL)
568 return E_SYSTEM_ERROR;
569 field->lines = new_lines;
570 field->lines_alloced++;
571 }
572
573 row = find_cur_line(field, pos);
574 #ifdef DEBUG
575 if (dbg_ok == TRUE) {
576 fprintf(dbg,
577 "split_line: enter: lines[%d].end = %d, lines[%d].length = %d\n",
578 row, field->lines[row].end, row,
579 field->lines[row].length);
580 }
581
582 assert(((field->lines[row].end < INT_MAX) &&
583 (field->lines[row].length < INT_MAX) &&
584 (field->lines[row].length > 0)));
585
586 #endif
587
588 /* if asked to split right where the line already starts then
589 * just return - nothing to do.
590 */
591 if (field->lines[row].start == pos)
592 return E_OK;
593
594 for (i = field->row_count - 1; i > row; i--) {
595 field->lines[i + 1] = field->lines[i];
596 }
597
598 field->lines[row + 1].end = field->lines[row].end;
599 field->lines[row].end = pos - 1;
600 field->lines[row].length = pos - field->lines[row].start;
601 field->lines[row + 1].start = pos;
602 field->lines[row + 1].length = field->lines[row + 1].end
603 - field->lines[row + 1].start + 1;
604
605 #ifdef DEBUG
606 assert(((field->lines[row + 1].end < INT_MAX) &&
607 (field->lines[row].end < INT_MAX) &&
608 (field->lines[row].length < INT_MAX) &&
609 (field->lines[row + 1].start < INT_MAX) &&
610 (field->lines[row + 1].length < INT_MAX) &&
611 (field->lines[row].length > 0) &&
612 (field->lines[row + 1].length > 0)));
613
614 if (dbg_ok == TRUE) {
615 fprintf(dbg,
616 "split_line: exit: lines[%d].end = %d, lines[%d].length = %d, ",
617 row, field->lines[row].end, row,
618 field->lines[row].length);
619 fprintf(dbg, "lines[%d].start = %d, lines[%d].end = %d, ",
620 row + 1, field->lines[row + 1].start, row + 1,
621 field->lines[row + 1].end);
622 fprintf(dbg, "lines[%d].length = %d, row_count = %d\n",
623 row + 1, field->lines[row + 1].length,
624 field->row_count + 1);
625 }
626 #endif
627
628 field->row_count++;
629
630 #ifdef DEBUG
631 if (dbg_ok == TRUE) {
632 bump_lines(field, 0, 0, FALSE); /* will report line data for us */
633 }
634 #endif
635
636 return E_OK;
637 }
638
639 /*
640 * skip the blanks in the given string, start at the index start and
641 * continue forward until either the end of the string or a non-blank
642 * character is found. Return the index of either the end of the string or
643 * the first non-blank character.
644 */
645 unsigned
646 _formi_skip_blanks(char *string, unsigned int start)
647 {
648 unsigned int i;
649
650 i = start;
651
652 while ((string[i] != '\0') && isblank(string[i]))
653 i++;
654
655 return i;
656 }
657
658 /*
659 * Return the index of the top left most field of the two given fields.
660 */
661 static int
662 _formi_top_left(FORM *form, int a, int b)
663 {
664 /* lower row numbers always win here.... */
665 if (form->fields[a]->form_row < form->fields[b]->form_row)
666 return a;
667
668 if (form->fields[a]->form_row > form->fields[b]->form_row)
669 return b;
670
671 /* rows must be equal, check columns */
672 if (form->fields[a]->form_col < form->fields[b]->form_col)
673 return a;
674
675 if (form->fields[a]->form_col > form->fields[b]->form_col)
676 return b;
677
678 /* if we get here fields must be in exactly the same place, punt */
679 return a;
680 }
681
682 /*
683 * Return the index to the field that is the bottom-right-most of the
684 * two given fields.
685 */
686 static int
687 _formi_bottom_right(FORM *form, int a, int b)
688 {
689 /* check the rows first, biggest row wins */
690 if (form->fields[a]->form_row > form->fields[b]->form_row)
691 return a;
692 if (form->fields[a]->form_row < form->fields[b]->form_row)
693 return b;
694
695 /* rows must be equal, check cols, biggest wins */
696 if (form->fields[a]->form_col > form->fields[b]->form_col)
697 return a;
698 if (form->fields[a]->form_col < form->fields[b]->form_col)
699 return b;
700
701 /* fields in the same place, punt */
702 return a;
703 }
704
705 /*
706 * Find the end of the current word in the string str, starting at
707 * offset - the end includes any trailing whitespace. If the end of
708 * the string is found before a new word then just return the offset
709 * to the end of the string.
710 */
711 static int
712 find_eow(char *str, unsigned int offset)
713 {
714 int start;
715
716 start = offset;
717 /* first skip any non-whitespace */
718 while ((str[start] != '\0') && !isblank(str[start]))
719 start++;
720
721 /* see if we hit the end of the string */
722 if (str[start] == '\0')
723 return start;
724
725 /* otherwise skip the whitespace.... */
726 while ((str[start] != '\0') && isblank(str[start]))
727 start++;
728
729 return start;
730 }
731
732 /*
733 * Find the beginning of the current word in the string str, starting
734 * at offset.
735 */
736 static int
737 find_sow(char *str, unsigned int offset)
738 {
739 int start;
740
741 start = offset;
742
743 if (start > 0) {
744 if (isblank(str[start]) || isblank(str[start - 1])) {
745 if (isblank(str[start - 1]))
746 start--;
747 /* skip the whitespace.... */
748 while ((start >= 0) && isblank(str[start]))
749 start--;
750 }
751 }
752
753 /* see if we hit the start of the string */
754 if (start < 0)
755 return 0;
756
757 /* now skip any non-whitespace */
758 while ((start >= 0) && !isblank(str[start]))
759 start--;
760
761 if (start > 0)
762 start++; /* last loop has us pointing at a space, adjust */
763
764 if (start < 0)
765 start = 0;
766
767 return start;
768 }
769
770 /*
771 * Scroll the field forward the given number of lines.
772 */
773 static void
774 _formi_scroll_fwd(FIELD *field, unsigned int amt)
775 {
776 /* check if we have lines to scroll */
777 if (field->row_count < (field->start_line + field->rows))
778 return;
779
780 field->start_line += min(amt,
781 field->row_count - field->start_line
782 - field->rows);
783 }
784
785 /*
786 * Scroll the field backward the given number of lines.
787 */
788 static void
789 _formi_scroll_back(FIELD *field, unsigned int amt)
790 {
791 if (field->start_line == 0)
792 return;
793
794 field->start_line -= min(field->start_line, amt);
795 }
796
797 /*
798 * Scroll the field forward the given number of characters.
799 */
800 void
801 _formi_hscroll_fwd(FIELD *field, int unsigned amt)
802 {
803 field->start_char += min(amt,
804 field->lines[field->start_line + field->cursor_ypos].end);
805 }
806
807 /*
808 * Scroll the field backward the given number of characters.
809 */
810 void
811 _formi_hscroll_back(FIELD *field, unsigned int amt)
812 {
813 field->start_char -= min(field->start_char, amt);
814 }
815
816 /*
817 * Find the different pages in the form fields and assign the form
818 * page_starts array with the information to find them.
819 */
820 int
821 _formi_find_pages(FORM *form)
822 {
823 int i, cur_page = 0;
824
825 if ((form->page_starts = (_FORMI_PAGE_START *)
826 malloc((form->max_page + 1) * sizeof(_FORMI_PAGE_START))) == NULL)
827 return E_SYSTEM_ERROR;
828
829 /* initialise the page starts array */
830 memset(form->page_starts, 0,
831 (form->max_page + 1) * sizeof(_FORMI_PAGE_START));
832
833 for (i =0; i < form->field_count; i++) {
834 if (form->fields[i]->page_break == 1)
835 cur_page++;
836 if (form->page_starts[cur_page].in_use == 0) {
837 form->page_starts[cur_page].in_use = 1;
838 form->page_starts[cur_page].first = i;
839 form->page_starts[cur_page].last = i;
840 form->page_starts[cur_page].top_left = i;
841 form->page_starts[cur_page].bottom_right = i;
842 } else {
843 form->page_starts[cur_page].last = i;
844 form->page_starts[cur_page].top_left =
845 _formi_top_left(form,
846 form->page_starts[cur_page].top_left,
847 i);
848 form->page_starts[cur_page].bottom_right =
849 _formi_bottom_right(form,
850 form->page_starts[cur_page].bottom_right,
851 i);
852 }
853 }
854
855 return E_OK;
856 }
857
858 /*
859 * Completely redraw the field of the given form.
860 */
861 void
862 _formi_redraw_field(FORM *form, int field)
863 {
864 unsigned int pre, post, flen, slen, i, row, start, last_row;
865 char *str;
866 FIELD *cur;
867 #ifdef DEBUG
868 char buffer[100];
869 #endif
870
871 cur = form->fields[field];
872 str = cur->buffers[0].string;
873 flen = cur->cols;
874 slen = 0;
875 start = 0;
876
877 if ((cur->row_count - cur->start_line) < cur->rows)
878 last_row = cur->row_count;
879 else
880 last_row = cur->start_line + cur->rows;
881
882 for (row = cur->start_line; row < last_row; row++) {
883 wmove(form->scrwin,
884 (int) (cur->form_row + row - cur->start_line),
885 (int) cur->form_col);
886 start = cur->lines[row].start;
887 slen = cur->lines[row].length;
888
889 if ((cur->opts & O_STATIC) == O_STATIC) {
890 switch (cur->justification) {
891 case JUSTIFY_RIGHT:
892 post = 0;
893 if (flen < slen)
894 pre = 0;
895 else
896 pre = flen - slen;
897 break;
898
899 case JUSTIFY_CENTER:
900 if (flen < slen) {
901 pre = 0;
902 post = 0;
903 } else {
904 pre = flen - slen;
905 post = pre = pre / 2;
906 /* get padding right if
907 centring is not even */
908 if ((post + pre + slen) < flen)
909 post++;
910 }
911 break;
912
913 case NO_JUSTIFICATION:
914 case JUSTIFY_LEFT:
915 default:
916 pre = 0;
917 if (flen <= slen)
918 post = 0;
919 else {
920 post = flen - slen;
921 if (post > flen)
922 post = flen;
923 }
924 break;
925 }
926 } else {
927 /* dynamic fields are not justified */
928 pre = 0;
929 if (flen <= slen)
930 post = 0;
931 else {
932 post = flen - slen;
933 if (post > flen)
934 post = flen;
935 }
936
937 /* but they do scroll.... */
938
939 if (pre > cur->start_char - start)
940 pre = pre - cur->start_char + start;
941 else
942 pre = 0;
943
944 if (slen > cur->start_char) {
945 slen -= cur->start_char;
946 post += cur->start_char;
947 if (post > flen)
948 post = flen;
949 } else {
950 slen = 0;
951 post = flen - pre;
952 }
953 }
954
955 if (form->cur_field == field)
956 wattrset(form->scrwin, cur->fore);
957 else
958 wattrset(form->scrwin, cur->back);
959
960 #ifdef DEBUG
961 if (_formi_create_dbg_file() == E_OK) {
962 fprintf(dbg,
963 "redraw_field: start=%d, pre=%d, slen=%d, flen=%d, post=%d, start_char=%d\n",
964 start, pre, slen, flen, post, cur->start_char);
965 if (str != NULL) {
966 strncpy(buffer,
967 &str[cur->start_char
968 + cur->lines[row].start], flen);
969 } else {
970 strcpy(buffer, "(null)");
971 }
972 buffer[flen] = '\0';
973 fprintf(dbg, "redraw_field: %s\n", buffer);
974 }
975 #endif
976
977 for (i = start + cur->start_char; i < pre; i++)
978 waddch(form->scrwin, cur->pad);
979
980 #ifdef DEBUG
981 fprintf(dbg, "redraw_field: will add %d chars\n",
982 min(slen, flen));
983 #endif
984 for (i = 0; i < min(slen, flen); i++)
985 {
986 #ifdef DEBUG
987 fprintf(dbg, "adding char str[%d]=%c\n",
988 i + cur->start_char + cur->lines[row].start,
989 str[i + cur->start_char
990 + cur->lines[row].start]);
991 #endif
992 if (((cur->opts & O_PUBLIC) != O_PUBLIC)) {
993 waddch(form->scrwin, cur->pad);
994 } else if ((cur->opts & O_VISIBLE) == O_VISIBLE) {
995 waddch(form->scrwin, str[i + cur->start_char
996 + cur->lines[row].start]);
997 } else {
998 waddch(form->scrwin, ' ');
999 }
1000 }
1001
1002 for (i = 0; i < post; i++)
1003 waddch(form->scrwin, cur->pad);
1004 }
1005
1006 for (row = cur->row_count - cur->start_line; row < cur->rows; row++) {
1007 wmove(form->scrwin, (int) (cur->form_row + row),
1008 (int) cur->form_col);
1009 for (i = 0; i < cur->cols; i++) {
1010 waddch(form->scrwin, cur->pad);
1011 }
1012 }
1013
1014 return;
1015 }
1016
1017 /*
1018 * Display the fields attached to the form that are on the current page
1019 * on the screen.
1020 *
1021 */
1022 int
1023 _formi_draw_page(FORM *form)
1024 {
1025 int i;
1026
1027 if (form->page_starts[form->page].in_use == 0)
1028 return E_BAD_ARGUMENT;
1029
1030 wclear(form->scrwin);
1031
1032 for (i = form->page_starts[form->page].first;
1033 i <= form->page_starts[form->page].last; i++)
1034 _formi_redraw_field(form, i);
1035
1036 return E_OK;
1037 }
1038
1039 /*
1040 * Add the character c at the position pos in buffer 0 of the given field
1041 */
1042 int
1043 _formi_add_char(FIELD *field, unsigned int pos, char c)
1044 {
1045 char *new;
1046 unsigned int new_size;
1047 int status;
1048
1049 /*
1050 * If buffer has not had a string before, set it to a blank
1051 * string. Everything should flow from there....
1052 */
1053 if (field->buffers[0].string == NULL) {
1054 set_field_buffer(field, 0, "");
1055 }
1056
1057 if (_formi_validate_char(field, c) != E_OK) {
1058 #ifdef DEBUG
1059 fprintf(dbg, "add_char: char %c failed char validation\n", c);
1060 #endif
1061 return E_INVALID_FIELD;
1062 }
1063
1064 #ifdef DEBUG
1065 fprintf(dbg, "add_char: pos=%d, char=%c\n", pos, c);
1066 fprintf(dbg,
1067 "add_char enter: xpos=%d, start=%d, length=%d(%d), allocated=%d\n",
1068 field->cursor_xpos, field->start_char,
1069 field->buffers[0].length, strlen(field->buffers[0].string),
1070 field->buffers[0].allocated);
1071 fprintf(dbg, "add_char enter: %s\n", field->buffers[0].string);
1072 fprintf(dbg, "add_char enter: buf0_status=%d\n", field->buf0_status);
1073 #endif
1074 if (((field->opts & O_BLANK) == O_BLANK) &&
1075 (field->buf0_status == FALSE) &&
1076 ((field->cursor_xpos + field->start_char) == 0)) {
1077 field->buffers[0].length = 0;
1078 field->buffers[0].string[0] = '\0';
1079 pos = 0;
1080 field->start_char = 0;
1081 field->start_line = 0;
1082 field->row_count = 1;
1083 field->cursor_xpos = 0;
1084 field->cursor_ypos = 0;
1085 field->lines[0].start = 0;
1086 field->lines[0].end = 0;
1087 field->lines[0].length = 0;
1088 }
1089
1090
1091 if ((field->overlay == 0)
1092 || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
1093 /* first check if the field can have more chars...*/
1094 if ((((field->opts & O_STATIC) == O_STATIC) &&
1095 (field->buffers[0].length >= (field->cols * field->rows))) ||
1096 (((field->opts & O_STATIC) != O_STATIC) &&
1097 /*XXXXX this is wrong - should check max row or col */
1098 ((field->max > 0) &&
1099 (field->buffers[0].length >= field->max))))
1100 return E_REQUEST_DENIED;
1101
1102 if (field->buffers[0].length + 1
1103 >= field->buffers[0].allocated) {
1104 new_size = field->buffers[0].allocated + 64
1105 - (field->buffers[0].allocated % 64);
1106 if ((new = (char *) realloc(field->buffers[0].string,
1107 new_size )) == NULL)
1108 return E_SYSTEM_ERROR;
1109 field->buffers[0].allocated = new_size;
1110 field->buffers[0].string = new;
1111 }
1112 }
1113
1114 if ((field->overlay == 0) && (field->buffers[0].length > pos)) {
1115 bcopy(&field->buffers[0].string[pos],
1116 &field->buffers[0].string[pos + 1],
1117 field->buffers[0].length - pos + 1);
1118 }
1119
1120 field->buffers[0].string[pos] = c;
1121 if (pos >= field->buffers[0].length) {
1122 /* make sure the string is terminated if we are at the
1123 * end of the string, the terminator would be missing
1124 * if we are are at the end of the field.
1125 */
1126 field->buffers[0].string[pos + 1] = '\0';
1127 }
1128
1129 /* only increment the length if we are inserting characters
1130 * OR if we are at the end of the field in overlay mode.
1131 */
1132 if ((field->overlay == 0)
1133 || ((field->overlay == 1) && (pos >= field->buffers[0].length))) {
1134 field->buffers[0].length++;
1135 bump_lines(field, (int) pos, 1, TRUE);
1136 }
1137
1138
1139 /* wrap the field, if needed */
1140 status = _formi_wrap_field(field, pos);
1141 if (status != E_OK) {
1142 /* wrap failed for some reason, back out the char insert */
1143 bcopy(&field->buffers[0].string[pos + 1],
1144 &field->buffers[0].string[pos],
1145 field->buffers[0].length - pos);
1146 field->buffers[0].length--;
1147 bump_lines(field, (int) pos, -1, TRUE);
1148 } else {
1149 field->buf0_status = TRUE;
1150 if ((field->rows + field->nrows) == 1) {
1151 if ((field->cursor_xpos < (field->cols - 1)) ||
1152 ((field->opts & O_STATIC) != O_STATIC))
1153 field->cursor_xpos++;
1154
1155 if (field->cursor_xpos > field->cols) {
1156 field->start_char++;
1157 field->cursor_xpos = field->cols;
1158 }
1159 } else {
1160 new_size = find_cur_line(field, pos);
1161 if (new_size >= field->rows) {
1162 field->cursor_ypos = field->rows - 1;
1163 field->start_line = field->row_count
1164 - field->cursor_ypos - 1;
1165 } else
1166 field->cursor_ypos = new_size;
1167
1168 field->cursor_xpos = pos
1169 - field->lines[field->cursor_ypos
1170 + field->start_line].start + 1;
1171
1172 /*
1173 * Annoying corner case - if we are right in
1174 * the bottom right corner of the field we
1175 * need to scroll the field one line so the
1176 * cursor is positioned correctly in the
1177 * field.
1178 */
1179 if ((field->cursor_xpos >= field->cols) &&
1180 (field->cursor_ypos == (field->rows - 1))) {
1181 field->cursor_ypos--;
1182 field->start_line++;
1183 }
1184 }
1185 }
1186
1187 #ifdef DEBUG
1188 assert((field->cursor_xpos < 400000)
1189 && (field->cursor_ypos < 400000)
1190 && (field->start_line < 400000));
1191
1192 fprintf(dbg,
1193 "add_char exit: xpos=%d, start=%d, length=%d(%d), allocated=%d\n",
1194 field->cursor_xpos, field->start_char,
1195 field->buffers[0].length, strlen(field->buffers[0].string),
1196 field->buffers[0].allocated);
1197 fprintf(dbg, "add_char exit: ypos=%d, start_line=%d\n",
1198 field->cursor_ypos, field->start_line);
1199 fprintf(dbg,"add_char exit: %s\n", field->buffers[0].string);
1200 fprintf(dbg, "add_char exit: buf0_status=%d\n", field->buf0_status);
1201 fprintf(dbg, "add_char exit: status = %s\n",
1202 (status == E_OK)? "OK" : "FAILED");
1203 #endif
1204 return status;
1205 }
1206
1207 /*
1208 * Manipulate the text in a field, this takes the given form and performs
1209 * the passed driver command on the current text field. Returns 1 if the
1210 * text field was modified.
1211 */
1212 int
1213 _formi_manipulate_field(FORM *form, int c)
1214 {
1215 FIELD *cur;
1216 char *str, saved;
1217 unsigned int i, start, end, pos, row, status, old_count;
1218 int len;
1219
1220 cur = form->fields[form->cur_field];
1221
1222 #ifdef DEBUG
1223 fprintf(dbg,
1224 "entry: xpos=%d, start_char=%d, length=%d, allocated=%d\n",
1225 cur->cursor_xpos, cur->start_char, cur->buffers[0].length,
1226 cur->buffers[0].allocated);
1227 fprintf(dbg, "entry: start_line=%d, ypos=%d\n", cur->start_line,
1228 cur->cursor_ypos);
1229 fprintf(dbg, "entry: string=");
1230 if (cur->buffers[0].string == NULL)
1231 fprintf(dbg, "(null)\n");
1232 else
1233 fprintf(dbg, "\"%s\"\n", cur->buffers[0].string);
1234 #endif
1235
1236 /* Cannot manipulate a null string! */
1237 if (cur->buffers[0].string == NULL)
1238 return E_REQUEST_DENIED;
1239
1240 switch (c) {
1241 case REQ_NEXT_CHAR:
1242 /* for a dynamic field allow an offset of one more
1243 * char so we can insert chars after end of string.
1244 * Static fields cannot do this so deny request if
1245 * cursor is at the end of the field.
1246 */
1247 if (((cur->opts & O_STATIC) == O_STATIC) &&
1248 (cur->cursor_xpos == cur->cols - 1) &&
1249 ((cur->rows + cur->nrows) == 1))
1250 return E_REQUEST_DENIED;
1251
1252 if ((cur->cursor_xpos + cur->start_char + 1)
1253 > cur->buffers[0].length)
1254 return E_REQUEST_DENIED;
1255
1256 if ((cur->rows + cur->nrows) == 1) {
1257 cur->cursor_xpos++;
1258 if (cur->cursor_xpos >= cur->cols - 1) {
1259 cur->cursor_xpos = cur->cols - 1;
1260 if ((cur->opts & O_STATIC) != O_STATIC)
1261 cur->start_char++;
1262 }
1263 } else {
1264 row = cur->start_line + cur->cursor_ypos;
1265 if (cur->cursor_xpos == (cur->lines[row].length - 1)) {
1266 if ((row + 1) >= cur->row_count)
1267 return E_REQUEST_DENIED;
1268
1269 cur->cursor_xpos = 0;
1270 if (cur->cursor_ypos == (cur->rows - 1))
1271 cur->start_line++;
1272 else
1273 cur->cursor_ypos++;
1274 } else
1275 cur->cursor_xpos++;
1276 }
1277
1278 break;
1279
1280 case REQ_PREV_CHAR:
1281 if ((cur->rows + cur->nrows) == 1) {
1282 if (cur->cursor_xpos == 0) {
1283 if (cur->start_char > 0)
1284 cur->start_char--;
1285 else
1286 return E_REQUEST_DENIED;
1287 } else
1288 cur->cursor_xpos--;
1289 } else {
1290 if ((cur->cursor_xpos == 0) &&
1291 (cur->cursor_ypos == 0) &&
1292 (cur->start_line == 0))
1293 return E_REQUEST_DENIED;
1294
1295 if (cur->cursor_xpos > 0) {
1296 cur->cursor_xpos--;
1297 } else {
1298 if (cur->cursor_ypos > 0)
1299 cur->cursor_ypos--;
1300 else
1301 cur->start_line--;
1302 cur->cursor_xpos =
1303 cur->lines[cur->start_line
1304 + cur->cursor_ypos].length
1305 - 1;
1306 }
1307 }
1308
1309 break;
1310
1311 case REQ_NEXT_LINE:
1312 if ((cur->start_line + cur->cursor_ypos + 1) >= cur->row_count)
1313 return E_REQUEST_DENIED;
1314
1315 if ((cur->cursor_ypos + 1) >= cur->rows) {
1316 cur->start_line++;
1317 } else
1318 cur->cursor_ypos++;
1319 break;
1320
1321 case REQ_PREV_LINE:
1322 if (cur->cursor_ypos == 0) {
1323 if (cur->start_line == 0)
1324 return E_REQUEST_DENIED;
1325 cur->start_line--;
1326 } else
1327 cur->cursor_ypos--;
1328 break;
1329
1330 case REQ_NEXT_WORD:
1331 start = cur->lines[cur->start_line + cur->cursor_ypos].start
1332 + cur->cursor_xpos + cur->start_char;
1333 str = cur->buffers[0].string;
1334
1335 start = find_eow(str, start);
1336
1337 /* check if we hit the end */
1338 if (str[start] == '\0')
1339 return E_REQUEST_DENIED;
1340
1341 /* otherwise we must have found the start of a word...*/
1342 if ((cur->rows + cur->nrows) == 1) {
1343 /* single line field */
1344 if (start - cur->start_char < cur->cols) {
1345 cur->cursor_xpos = start;
1346 } else {
1347 cur->start_char = start;
1348 cur->cursor_xpos = 0;
1349 }
1350 } else {
1351 /* multiline field */
1352 row = find_cur_line(cur, start);
1353 cur->cursor_xpos = start - cur->lines[row].start;
1354 if (row != (cur->start_line + cur->cursor_ypos)) {
1355 if (cur->cursor_ypos == (cur->rows - 1)) {
1356 cur->start_line = row - cur->rows + 1;
1357 } else {
1358 cur->cursor_ypos = row
1359 - cur->start_line;
1360 }
1361 }
1362 }
1363 break;
1364
1365 case REQ_PREV_WORD:
1366 start = cur->start_char + cur->cursor_xpos
1367 + cur->lines[cur->start_line + cur->cursor_ypos].start;
1368 if (cur->start_char > 0)
1369 start--;
1370
1371 if (start == 0)
1372 return E_REQUEST_DENIED;
1373
1374 str = cur->buffers[0].string;
1375
1376 start = find_sow(str, start);
1377
1378 if ((cur->rows + cur->nrows) == 1) {
1379 /* single line field */
1380 if (start - cur->start_char > 0) {
1381 cur->cursor_xpos = start;
1382 } else {
1383 cur->start_char = start;
1384 cur->cursor_xpos = 0;
1385 }
1386 } else {
1387 /* multiline field */
1388 row = find_cur_line(cur, start);
1389 cur->cursor_xpos = start - cur->lines[row].start;
1390 if (row != (cur->start_line + cur->cursor_ypos)) {
1391 if (cur->cursor_ypos == 0) {
1392 cur->start_line = row;
1393 } else {
1394 if (cur->start_line > row) {
1395 cur->start_line = row;
1396 cur->cursor_ypos = 0;
1397 } else {
1398 cur->cursor_ypos = row -
1399 cur->start_line;
1400 }
1401 }
1402 }
1403 }
1404
1405 break;
1406
1407 case REQ_BEG_FIELD:
1408 cur->start_char = 0;
1409 cur->start_line = 0;
1410 cur->cursor_xpos = 0;
1411 cur->cursor_ypos = 0;
1412 break;
1413
1414 case REQ_BEG_LINE:
1415 cur->cursor_xpos = 0;
1416 break;
1417
1418 case REQ_END_FIELD:
1419 if (cur->row_count > cur->rows) {
1420 cur->start_line = cur->row_count - cur->rows;
1421 cur->cursor_ypos = cur->rows - 1;
1422 } else {
1423 cur->start_line = 0;
1424 cur->cursor_ypos = cur->row_count - 1;
1425 }
1426
1427 /* we fall through here deliberately, we are on the
1428 * correct row, now we need to get to the end of the
1429 * line.
1430 */
1431 /* FALLTHRU */
1432
1433 case REQ_END_LINE:
1434 start = cur->lines[cur->start_line + cur->cursor_ypos].start;
1435 end = cur->lines[cur->start_line + cur->cursor_ypos].end;
1436
1437 if ((cur->rows + cur->nrows) == 1) {
1438 if (end - start > cur->cols - 1) {
1439 cur->cursor_xpos = cur->cols - 1;
1440 cur->start_char = end - cur->cols;
1441 if ((cur->opts & O_STATIC) != O_STATIC)
1442 cur->start_char++;
1443 } else {
1444 cur->cursor_xpos = end - start + 1;
1445 if (((cur->opts & O_STATIC) == O_STATIC) &&
1446 ((end - start) == (cur->cols - 1)))
1447 cur->cursor_xpos--;
1448
1449 cur->start_char = start;
1450 }
1451 } else {
1452 cur->cursor_xpos = end - start + 1;
1453 }
1454 break;
1455
1456 case REQ_LEFT_CHAR:
1457 if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
1458 && (cur->start_line == 0) && (cur->cursor_ypos == 0))
1459 return E_REQUEST_DENIED;
1460
1461 if (cur->cursor_xpos == 0) {
1462 if ((cur->rows + cur->nrows) == 1) {
1463 if (cur->start_char > 0)
1464 cur->start_char--;
1465 else
1466 return E_REQUEST_DENIED;
1467 } else {
1468 if ((cur->cursor_ypos == 0) &&
1469 (cur->start_line == 0))
1470 return E_REQUEST_DENIED;
1471
1472 if (cur->cursor_ypos == 0)
1473 cur->start_line--;
1474 else
1475 cur->cursor_ypos--;
1476
1477 cur->cursor_xpos =
1478 cur->lines[cur->cursor_ypos
1479 + cur->start_line].length;
1480 }
1481 } else
1482 cur->cursor_xpos--;
1483 break;
1484
1485 case REQ_RIGHT_CHAR:
1486 pos = cur->start_char + cur->cursor_xpos;
1487 row = cur->start_line + cur->cursor_ypos;
1488 end = cur->lines[row].end;
1489
1490 if (cur->buffers[0].string[pos] == '\0')
1491 return E_REQUEST_DENIED;
1492
1493 #ifdef DEBUG
1494 fprintf(dbg, "req_right_char enter: start=%d, xpos=%d, c=%c\n",
1495 cur->start_char, cur->cursor_xpos,
1496 cur->buffers[0].string[pos]);
1497 #endif
1498
1499 if (pos == end) {
1500 start = pos + 1;
1501 if ((cur->buffers[0].length <= start)
1502 || ((row + 1) >= cur->row_count))
1503 return E_REQUEST_DENIED;
1504
1505 if ((cur->cursor_ypos + 1) >= cur->rows) {
1506 cur->start_line++;
1507 cur->cursor_ypos = cur->rows - 1;
1508 } else
1509 cur->cursor_ypos++;
1510
1511 cur->cursor_xpos = 0;
1512 } else {
1513 if (((cur->rows + cur->nrows) == 1) &&
1514 (cur->cursor_xpos == cur->cols - 1))
1515 cur->start_char++;
1516 else
1517 cur->cursor_xpos++;
1518 }
1519 #ifdef DEBUG
1520 fprintf(dbg, "req_right_char exit: start=%d, xpos=%d, c=%c\n",
1521 cur->start_char, cur->cursor_xpos,
1522 cur->buffers[0].string[cur->start_char +
1523 cur->cursor_xpos]);
1524 #endif
1525 break;
1526
1527 case REQ_UP_CHAR:
1528 if (cur->cursor_ypos == 0) {
1529 if (cur->start_line == 0)
1530 return E_REQUEST_DENIED;
1531
1532 cur->start_line--;
1533 } else
1534 cur->cursor_ypos--;
1535
1536 row = cur->start_line + cur->cursor_ypos;
1537
1538 if (cur->cursor_xpos > cur->lines[row].length)
1539 cur->cursor_xpos = cur->lines[row].length;
1540 break;
1541
1542 case REQ_DOWN_CHAR:
1543 if (cur->cursor_ypos == cur->rows - 1) {
1544 if (cur->start_line + cur->rows == cur->row_count)
1545 return E_REQUEST_DENIED;
1546 cur->start_line++;
1547 } else
1548 cur->cursor_ypos++;
1549
1550 row = cur->start_line + cur->cursor_ypos;
1551 if (cur->cursor_xpos > cur->lines[row].length)
1552 cur->cursor_xpos = cur->lines[row].length;
1553 break;
1554
1555 case REQ_NEW_LINE:
1556 if ((status = split_line(cur,
1557 cur->start_char + cur->cursor_xpos)) != E_OK)
1558 return status;
1559 break;
1560
1561 case REQ_INS_CHAR:
1562 _formi_add_char(cur, cur->start_char + cur->cursor_xpos,
1563 cur->pad);
1564 break;
1565
1566 case REQ_INS_LINE:
1567 start = cur->lines[cur->start_line + cur->cursor_ypos].start;
1568 if ((status = split_line(cur, start)) != E_OK)
1569 return status;
1570 break;
1571
1572 case REQ_DEL_CHAR:
1573 if (cur->buffers[0].length == 0)
1574 return E_REQUEST_DENIED;
1575
1576 row = cur->start_line + cur->cursor_ypos;
1577 start = cur->start_char + cur->cursor_xpos
1578 + cur->lines[row].start;
1579 end = cur->buffers[0].length;
1580 if (start == cur->lines[row].end) {
1581 if ((cur->rows + cur->nrows) > 1) {
1582 if (cur->row_count > 1) {
1583 if (_formi_join_line(cur,
1584 start,
1585 JOIN_NEXT_NW)
1586 != E_OK) {
1587 return E_REQUEST_DENIED;
1588 }
1589 } else
1590 return E_REQUEST_DENIED;
1591 } else
1592 return E_REQUEST_DENIED;
1593 }
1594
1595 saved = cur->buffers[0].string[start];
1596 bcopy(&cur->buffers[0].string[start + 1],
1597 &cur->buffers[0].string[start],
1598 (unsigned) end - start + 1);
1599 cur->buffers[0].length--;
1600 bump_lines(cur, _FORMI_USE_CURRENT, -1, TRUE);
1601 if ((cur->rows + cur->nrows) > 1) {
1602 if (_formi_wrap_field(cur, start) != E_OK) {
1603 bcopy(&cur->buffers[0].string[start],
1604 &cur->buffers[0].string[start + 1],
1605 (unsigned) end - start);
1606 cur->buffers[0].length++;
1607 cur->buffers[0].string[start] = saved;
1608 bump_lines(cur, _FORMI_USE_CURRENT, 1, TRUE);
1609 _formi_wrap_field(cur, start);
1610 return E_REQUEST_DENIED;
1611 }
1612 }
1613 break;
1614
1615 case REQ_DEL_PREV:
1616 if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
1617 && (cur->start_line == 0) && (cur->cursor_ypos == 0))
1618 return E_REQUEST_DENIED;
1619
1620 row = cur->start_line + cur->cursor_ypos;
1621 start = cur->cursor_xpos + cur->start_char
1622 + cur->lines[row].start;
1623 end = cur->buffers[0].length;
1624
1625 if ((cur->start_char + cur->cursor_xpos) == 0) {
1626 if (_formi_join_line(cur, cur->lines[row].start,
1627 JOIN_PREV_NW) != E_OK) {
1628 return E_REQUEST_DENIED;
1629 }
1630 }
1631
1632 saved = cur->buffers[0].string[start - 1];
1633 bcopy(&cur->buffers[0].string[start],
1634 &cur->buffers[0].string[start - 1],
1635 (unsigned) end - start + 1);
1636 bump_lines(cur, (int) start - 1, -1, TRUE);
1637 cur->buffers[0].length--;
1638
1639 if ((cur->rows + cur->nrows) == 1) {
1640 if ((cur->cursor_xpos == 0) && (cur->start_char > 0))
1641 cur->start_char--;
1642 else if ((cur->cursor_xpos == cur->cols - 1)
1643 && (cur->start_char > 0))
1644 cur->start_char--;
1645 else if (cur->cursor_xpos > 0)
1646 cur->cursor_xpos--;
1647 } else {
1648 pos = start - 1;
1649 if (pos >= cur->buffers[0].length)
1650 pos = cur->buffers[0].length - 1;
1651
1652 if ((_formi_wrap_field(cur, pos) != E_OK)) {
1653 bcopy(&cur->buffers[0].string[start - 1],
1654 &cur->buffers[0].string[start],
1655 (unsigned) end - start);
1656 cur->buffers[0].length++;
1657 cur->buffers[0].string[start - 1] = saved;
1658 bump_lines(cur, (int) start - 1, 1, TRUE);
1659 _formi_wrap_field(cur, pos);
1660 return E_REQUEST_DENIED;
1661 }
1662
1663 row = find_cur_line(cur, pos);
1664 cur->cursor_xpos = start - cur->lines[row].start - 1;
1665
1666 if (row >= cur->rows)
1667 cur->start_line = row - cur->cursor_ypos;
1668 else {
1669 cur->start_line = 0;
1670 cur->cursor_ypos = row;
1671 }
1672 }
1673 break;
1674
1675 case REQ_DEL_LINE:
1676 row = cur->start_line + cur->cursor_ypos;
1677 start = cur->lines[row].start;
1678 end = cur->lines[row].end;
1679 bcopy(&cur->buffers[0].string[end + 1],
1680 &cur->buffers[0].string[start],
1681 (unsigned) cur->buffers[0].length - end + 1);
1682
1683 if (((cur->rows + cur->nrows) == 1) ||
1684 (cur->row_count == 1)) {
1685 /* single line case */
1686 cur->buffers[0].length = 0;
1687 cur->lines[0].end = cur->lines[0].length = 0;
1688 cur->cursor_xpos = cur->cursor_ypos = 0;
1689 } else {
1690 /* multiline field */
1691 old_count = cur->row_count;
1692 cur->row_count--;
1693 if (cur->row_count == 0)
1694 cur->row_count = 1;
1695
1696 if (cur->row_count > 1)
1697 bcopy(&cur->lines[row + 1],
1698 &cur->lines[row],
1699 (unsigned) (cur->row_count - row)
1700 * sizeof(struct _formi_field_lines));
1701
1702 cur->lines[row].start = start;
1703 len = start - end - 1; /* yes, this is negative */
1704
1705 if (row < (cur->row_count - 1))
1706 bump_lines(cur, (int) start, len, FALSE);
1707 else if (old_count == 1) {
1708 cur->lines[0].end = cur->lines[0].length = 0;
1709 cur->cursor_xpos = 0;
1710 cur->cursor_ypos = 0;
1711 } else if (cur->row_count == 1) {
1712 cur->lines[0].length = cur->buffers[0].length
1713 + len;
1714 cur->lines[0].end = cur->lines[0].length - 1;
1715 }
1716
1717 cur->buffers[0].length += len;
1718
1719 if (row > (cur->row_count - 1)) {
1720 row--;
1721 if (cur->cursor_ypos == 0) {
1722 if (cur->start_line > 0) {
1723 cur->start_line--;
1724 }
1725 } else {
1726 cur->cursor_ypos--;
1727 }
1728 }
1729
1730 if (old_count > 1) {
1731 if (cur->cursor_xpos > cur->lines[row].length)
1732 cur->cursor_xpos =
1733 cur->lines[row].length - 1;
1734 if (row >= cur->rows)
1735 cur->start_line = row
1736 - cur->cursor_ypos;
1737 else {
1738 cur->start_line = 0;
1739 cur->cursor_ypos = row;
1740 }
1741 }
1742 }
1743 break;
1744
1745 case REQ_DEL_WORD:
1746 start = cur->start_char + cur->cursor_xpos;
1747 end = find_eow(cur->buffers[0].string, start);
1748 start = find_sow(cur->buffers[0].string, start);
1749 bcopy(&cur->buffers[0].string[end + 1],
1750 &cur->buffers[0].string[start],
1751 (unsigned) cur->buffers[0].length - end + 1);
1752 len = end - start;
1753 cur->buffers[0].length -= len;
1754 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
1755
1756 if (cur->cursor_xpos > cur->lines[row].length)
1757 cur->cursor_xpos = cur->lines[row].length;
1758 break;
1759
1760 case REQ_CLR_EOL:
1761 row = cur->start_line + cur->cursor_ypos;
1762 start = cur->start_char + cur->cursor_xpos;
1763 end = cur->lines[row].end;
1764 len = end - start;
1765 bcopy(&cur->buffers[0].string[end + 1],
1766 &cur->buffers[0].string[start],
1767 cur->buffers[0].length - end + 1);
1768 cur->buffers[0].length -= len;
1769 bump_lines(cur, _FORMI_USE_CURRENT, - (int) len, TRUE);
1770
1771 if (cur->cursor_xpos > cur->lines[row].length)
1772 cur->cursor_xpos = cur->lines[row].length;
1773 break;
1774
1775 case REQ_CLR_EOF:
1776 row = cur->start_line + cur->cursor_ypos;
1777 cur->buffers[0].string[cur->start_char
1778 + cur->cursor_xpos] = '\0';
1779 cur->buffers[0].length = strlen(cur->buffers[0].string);
1780 cur->lines[row].end = cur->buffers[0].length;
1781 cur->lines[row].length = cur->lines[row].end
1782 - cur->lines[row].start;
1783
1784 for (i = cur->start_char + cur->cursor_xpos;
1785 i < cur->buffers[0].length; i++)
1786 cur->buffers[0].string[i] = cur->pad;
1787 break;
1788
1789 case REQ_CLR_FIELD:
1790 cur->buffers[0].string[0] = '\0';
1791 cur->buffers[0].length = 0;
1792 cur->row_count = 1;
1793 cur->start_line = 0;
1794 cur->cursor_ypos = 0;
1795 cur->cursor_xpos = 0;
1796 cur->start_char = 0;
1797 cur->lines[0].start = 0;
1798 cur->lines[0].end = 0;
1799 cur->lines[0].length = 0;
1800 break;
1801
1802 case REQ_OVL_MODE:
1803 cur->overlay = 1;
1804 break;
1805
1806 case REQ_INS_MODE:
1807 cur->overlay = 0;
1808 break;
1809
1810 case REQ_SCR_FLINE:
1811 _formi_scroll_fwd(cur, 1);
1812 break;
1813
1814 case REQ_SCR_BLINE:
1815 _formi_scroll_back(cur, 1);
1816 break;
1817
1818 case REQ_SCR_FPAGE:
1819 _formi_scroll_fwd(cur, cur->rows);
1820 break;
1821
1822 case REQ_SCR_BPAGE:
1823 _formi_scroll_back(cur, cur->rows);
1824 break;
1825
1826 case REQ_SCR_FHPAGE:
1827 _formi_scroll_fwd(cur, cur->rows / 2);
1828 break;
1829
1830 case REQ_SCR_BHPAGE:
1831 _formi_scroll_back(cur, cur->rows / 2);
1832 break;
1833
1834 case REQ_SCR_FCHAR:
1835 _formi_hscroll_fwd(cur, 1);
1836 break;
1837
1838 case REQ_SCR_BCHAR:
1839 _formi_hscroll_back(cur, 1);
1840 break;
1841
1842 case REQ_SCR_HFLINE:
1843 _formi_hscroll_fwd(cur, cur->cols);
1844 break;
1845
1846 case REQ_SCR_HBLINE:
1847 _formi_hscroll_back(cur, cur->cols);
1848 break;
1849
1850 case REQ_SCR_HFHALF:
1851 _formi_hscroll_fwd(cur, cur->cols / 2);
1852 break;
1853
1854 case REQ_SCR_HBHALF:
1855 _formi_hscroll_back(cur, cur->cols / 2);
1856 break;
1857
1858 default:
1859 return 0;
1860 }
1861
1862 #ifdef DEBUG
1863 fprintf(dbg, "exit: xpos=%d, start_char=%d, length=%d, allocated=%d\n",
1864 cur->cursor_xpos, cur->start_char, cur->buffers[0].length,
1865 cur->buffers[0].allocated);
1866 fprintf(dbg, "exit: start_line=%d, ypos=%d\n", cur->start_line,
1867 cur->cursor_ypos);
1868 fprintf(dbg, "exit: string=\"%s\"\n", cur->buffers[0].string);
1869 #endif
1870 return 1;
1871 }
1872
1873 /*
1874 * Validate the give character by passing it to any type character
1875 * checking routines, if they exist.
1876 */
1877 int
1878 _formi_validate_char(FIELD *field, char c)
1879 {
1880 int ret_val;
1881
1882 if (field->type == NULL)
1883 return E_OK;
1884
1885 ret_val = E_INVALID_FIELD;
1886 _formi_do_char_validation(field, field->type, c, &ret_val);
1887
1888 return ret_val;
1889 }
1890
1891
1892 /*
1893 * Perform the validation of the character, invoke all field_type validation
1894 * routines. If the field is ok then update ret_val to E_OK otherwise
1895 * ret_val is not changed.
1896 */
1897 static void
1898 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
1899 {
1900 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
1901 _formi_do_char_validation(field, type->link->next, c, ret_val);
1902 _formi_do_char_validation(field, type->link->prev, c, ret_val);
1903 } else {
1904 if (type->char_check == NULL)
1905 *ret_val = E_OK;
1906 else {
1907 if (type->char_check((int)(unsigned char) c,
1908 field->args) == TRUE)
1909 *ret_val = E_OK;
1910 }
1911 }
1912 }
1913
1914 /*
1915 * Validate the current field. If the field validation returns success then
1916 * return E_OK otherwise return E_INVALID_FIELD.
1917 *
1918 */
1919 int
1920 _formi_validate_field(FORM *form)
1921 {
1922 FIELD *cur;
1923 char *bp;
1924 int ret_val, count;
1925
1926
1927 if ((form == NULL) || (form->fields == NULL) ||
1928 (form->fields[0] == NULL))
1929 return E_INVALID_FIELD;
1930
1931 cur = form->fields[form->cur_field];
1932
1933 bp = cur->buffers[0].string;
1934 count = _formi_skip_blanks(bp, 0);
1935
1936 /* check if we have a null field, depending on the nullok flag
1937 * this may be acceptable or not....
1938 */
1939 if (cur->buffers[0].string[count] == '\0') {
1940 if ((cur->opts & O_NULLOK) == O_NULLOK)
1941 return E_OK;
1942 else
1943 return E_INVALID_FIELD;
1944 }
1945
1946 /* check if an unmodified field is ok */
1947 if (cur->buf0_status == 0) {
1948 if ((cur->opts & O_PASSOK) == O_PASSOK)
1949 return E_OK;
1950 else
1951 return E_INVALID_FIELD;
1952 }
1953
1954 /* if there is no type then just accept the field */
1955 if (cur->type == NULL)
1956 return E_OK;
1957
1958 ret_val = E_INVALID_FIELD;
1959 _formi_do_validation(cur, cur->type, &ret_val);
1960
1961 return ret_val;
1962 }
1963
1964 /*
1965 * Perform the validation of the field, invoke all field_type validation
1966 * routines. If the field is ok then update ret_val to E_OK otherwise
1967 * ret_val is not changed.
1968 */
1969 static void
1970 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
1971 {
1972 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
1973 _formi_do_validation(field, type->link->next, ret_val);
1974 _formi_do_validation(field, type->link->prev, ret_val);
1975 } else {
1976 if (type->field_check == NULL)
1977 *ret_val = E_OK;
1978 else {
1979 if (type->field_check(field, field_buffer(field, 0))
1980 == TRUE)
1981 *ret_val = E_OK;
1982 }
1983 }
1984 }
1985
1986 /*
1987 * Select the next/previous choice for the field, the driver command
1988 * selecting the direction will be passed in c. Return 1 if a choice
1989 * selection succeeded, 0 otherwise.
1990 */
1991 int
1992 _formi_field_choice(FORM *form, int c)
1993 {
1994 FIELDTYPE *type;
1995 FIELD *field;
1996
1997 if ((form == NULL) || (form->fields == NULL) ||
1998 (form->fields[0] == NULL) ||
1999 (form->fields[form->cur_field]->type == NULL))
2000 return 0;
2001
2002 field = form->fields[form->cur_field];
2003 type = field->type;
2004
2005 switch (c) {
2006 case REQ_NEXT_CHOICE:
2007 if (type->next_choice == NULL)
2008 return 0;
2009 else
2010 return type->next_choice(field,
2011 field_buffer(field, 0));
2012
2013 case REQ_PREV_CHOICE:
2014 if (type->prev_choice == NULL)
2015 return 0;
2016 else
2017 return type->prev_choice(field,
2018 field_buffer(field, 0));
2019
2020 default: /* should never happen! */
2021 return 0;
2022 }
2023 }
2024
2025 /*
2026 * Update the fields if they have changed. The parameter old has the
2027 * previous current field as the current field may have been updated by
2028 * the driver. Return 1 if the form page needs updating.
2029 *
2030 */
2031 int
2032 _formi_update_field(FORM *form, int old_field)
2033 {
2034 int cur, i;
2035
2036 cur = form->cur_field;
2037
2038 if (old_field != cur) {
2039 if (!((cur >= form->page_starts[form->page].first) &&
2040 (cur <= form->page_starts[form->page].last))) {
2041 /* not on same page any more */
2042 for (i = 0; i < form->max_page; i++) {
2043 if ((form->page_starts[i].in_use == 1) &&
2044 (form->page_starts[i].first <= cur) &&
2045 (form->page_starts[i].last >= cur)) {
2046 form->page = i;
2047 return 1;
2048 }
2049 }
2050 }
2051 }
2052
2053 _formi_redraw_field(form, old_field);
2054 _formi_redraw_field(form, form->cur_field);
2055 return 0;
2056 }
2057
2058 /*
2059 * Compare function for the field sorting
2060 *
2061 */
2062 static int
2063 field_sort_compare(const void *one, const void *two)
2064 {
2065 const FIELD *a, *b;
2066 int tl;
2067
2068 /* LINTED const castaway; we don't modify these! */
2069 a = (const FIELD *) *((const FIELD **) one);
2070 b = (const FIELD *) *((const FIELD **) two);
2071
2072 if (a == NULL)
2073 return 1;
2074
2075 if (b == NULL)
2076 return -1;
2077
2078 /*
2079 * First check the page, we want the fields sorted by page.
2080 *
2081 */
2082 if (a->page != b->page)
2083 return ((a->page > b->page)? 1 : -1);
2084
2085 tl = _formi_top_left(a->parent, a->index, b->index);
2086
2087 /*
2088 * sort fields left to right, top to bottom so the top left is
2089 * the less than value....
2090 */
2091 return ((tl == a->index)? -1 : 1);
2092 }
2093
2094 /*
2095 * Sort the fields in a form ready for driver traversal.
2096 */
2097 void
2098 _formi_sort_fields(FORM *form)
2099 {
2100 FIELD **sort_area;
2101 int i;
2102
2103 CIRCLEQ_INIT(&form->sorted_fields);
2104
2105 if ((sort_area = (FIELD **) malloc(sizeof(FIELD *) * form->field_count))
2106 == NULL)
2107 return;
2108
2109 bcopy(form->fields, sort_area, sizeof(FIELD *) * form->field_count);
2110 qsort(sort_area, (unsigned) form->field_count, sizeof(FIELD *),
2111 field_sort_compare);
2112
2113 for (i = 0; i < form->field_count; i++)
2114 CIRCLEQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
2115
2116 free(sort_area);
2117 }
2118
2119 /*
2120 * Set the neighbours for all the fields in the given form.
2121 */
2122 void
2123 _formi_stitch_fields(FORM *form)
2124 {
2125 int above_row, below_row, end_above, end_below, cur_row, real_end;
2126 FIELD *cur, *above, *below;
2127
2128 /*
2129 * check if the sorted fields circle queue is empty, just
2130 * return if it is.
2131 */
2132 if (CIRCLEQ_EMPTY(&form->sorted_fields))
2133 return;
2134
2135 /* initially nothing is above..... */
2136 above_row = -1;
2137 end_above = TRUE;
2138 above = NULL;
2139
2140 /* set up the first field as the current... */
2141 cur = CIRCLEQ_FIRST(&form->sorted_fields);
2142 cur_row = cur->form_row;
2143
2144 /* find the first field on the next row if any */
2145 below = CIRCLEQ_NEXT(cur, glue);
2146 below_row = -1;
2147 end_below = TRUE;
2148 real_end = TRUE;
2149 while (below != (void *)&form->sorted_fields) {
2150 if (below->form_row != cur_row) {
2151 below_row = below->form_row;
2152 end_below = FALSE;
2153 real_end = FALSE;
2154 break;
2155 }
2156 below = CIRCLEQ_NEXT(below, glue);
2157 }
2158
2159 /* walk the sorted fields, setting the neighbour pointers */
2160 while (cur != (void *) &form->sorted_fields) {
2161 if (cur == CIRCLEQ_FIRST(&form->sorted_fields))
2162 cur->left = NULL;
2163 else
2164 cur->left = CIRCLEQ_PREV(cur, glue);
2165
2166 if (cur == CIRCLEQ_LAST(&form->sorted_fields))
2167 cur->right = NULL;
2168 else
2169 cur->right = CIRCLEQ_NEXT(cur, glue);
2170
2171 if (end_above == TRUE)
2172 cur->up = NULL;
2173 else {
2174 cur->up = above;
2175 above = CIRCLEQ_NEXT(above, glue);
2176 if (above_row != above->form_row) {
2177 end_above = TRUE;
2178 above_row = above->form_row;
2179 }
2180 }
2181
2182 if (end_below == TRUE)
2183 cur->down = NULL;
2184 else {
2185 cur->down = below;
2186 below = CIRCLEQ_NEXT(below, glue);
2187 if (below == (void *) &form->sorted_fields) {
2188 end_below = TRUE;
2189 real_end = TRUE;
2190 } else if (below_row != below->form_row) {
2191 end_below = TRUE;
2192 below_row = below->form_row;
2193 }
2194 }
2195
2196 cur = CIRCLEQ_NEXT(cur, glue);
2197 if ((cur != (void *) &form->sorted_fields)
2198 && (cur_row != cur->form_row)) {
2199 cur_row = cur->form_row;
2200 if (end_above == FALSE) {
2201 for (; above != CIRCLEQ_FIRST(&form->sorted_fields);
2202 above = CIRCLEQ_NEXT(above, glue)) {
2203 if (above->form_row != above_row) {
2204 above_row = above->form_row;
2205 break;
2206 }
2207 }
2208 } else if (above == NULL) {
2209 above = CIRCLEQ_FIRST(&form->sorted_fields);
2210 end_above = FALSE;
2211 above_row = above->form_row;
2212 } else
2213 end_above = FALSE;
2214
2215 if (end_below == FALSE) {
2216 while (below_row == below->form_row) {
2217 below = CIRCLEQ_NEXT(below,
2218 glue);
2219 if (below ==
2220 (void *)&form->sorted_fields) {
2221 real_end = TRUE;
2222 end_below = TRUE;
2223 break;
2224 }
2225 }
2226
2227 if (below != (void *)&form->sorted_fields)
2228 below_row = below->form_row;
2229 } else if (real_end == FALSE)
2230 end_below = FALSE;
2231
2232 }
2233 }
2234 }
2235