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