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