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