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