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