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