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