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