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