1 /* $NetBSD: internals.c,v 1.40 2021/04/13 13:13:04 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1998-1999 Brett Lymn 5 * (blymn@baea.com.au, brett_lymn@yahoo.com.au) 6 * All rights reserved. 7 * 8 * This code has been donated to The NetBSD Foundation by the Author. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 * 29 * 30 */ 31 32 #include <sys/cdefs.h> 33 __RCSID("$NetBSD: internals.c,v 1.40 2021/04/13 13:13:04 christos Exp $"); 34 35 #include <limits.h> 36 #include <ctype.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <strings.h> 40 #include <assert.h> 41 #include <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 if (form->cur_field == field) 1610 wattrset(form->scrwin, cur->fore); 1611 else 1612 wattrset(form->scrwin, cur->back); 1613 1614 str = &row->string[cur->start_char]; 1615 1616 #ifdef DEBUG 1617 _formi_dbg_printf( 1618 "%s: start=%d, pre=%d, slen=%d, flen=%d, post=%d, " 1619 "start_char=%d\n", __func__, 1620 start, pre, slen, flen, post, cur->start_char); 1621 if (str != NULL) { 1622 if (row->expanded != 0) { 1623 strncpy(buffer, str, flen); 1624 } else { 1625 strcpy(buffer, "(empty)"); 1626 } 1627 } else { 1628 strcpy(buffer, "(null)"); 1629 } 1630 buffer[flen] = '\0'; 1631 _formi_dbg_printf("%s: %s\n", __func__, buffer); 1632 #endif 1633 1634 for (i = start + cur->start_char; i < pre; i++) 1635 waddch(form->scrwin, cur->pad); 1636 1637 _formi_dbg_printf("%s: will add %d chars\n", __func__, 1638 min(slen, flen)); 1639 for (i = 0, cpos = cur->start_char; i < min(slen, flen); 1640 i++, str++, cpos++) 1641 { 1642 c = *str; 1643 tab = 0; /* just to shut gcc up */ 1644 _formi_dbg_printf("adding char str[%d]=%c\n", 1645 cpos + cur->start_char, c); 1646 if (((cur->opts & O_PUBLIC) != O_PUBLIC)) { 1647 if (c == '\t') 1648 tab = add_tab(form, row, cpos, 1649 cur->pad); 1650 else 1651 waddch(form->scrwin, cur->pad); 1652 } else if ((cur->opts & O_VISIBLE) == O_VISIBLE) { 1653 if (c == '\t') 1654 tab = add_tab(form, row, cpos, ' '); 1655 else 1656 waddch(form->scrwin, c); 1657 } else { 1658 if (c == '\t') 1659 tab = add_tab(form, row, cpos, ' '); 1660 else 1661 waddch(form->scrwin, ' '); 1662 } 1663 1664 /* 1665 * If we have had a tab then skip forward 1666 * the requisite number of chars to keep 1667 * things in sync. 1668 */ 1669 if (c == '\t') 1670 i += tab - 1; 1671 } 1672 1673 for (i = 0; i < post; i++) 1674 waddch(form->scrwin, cur->pad); 1675 } 1676 1677 for (i = line; i < cur->rows; i++) { 1678 wmove(form->scrwin, (int) (cur->form_row + i), 1679 (int) cur->form_col); 1680 1681 if (form->cur_field == field) 1682 wattrset(form->scrwin, cur->fore); 1683 else 1684 wattrset(form->scrwin, cur->back); 1685 1686 for (j = 0; j < cur->cols; j++) { 1687 waddch(form->scrwin, cur->pad); 1688 } 1689 } 1690 1691 wattrset(form->scrwin, cur->back); 1692 return; 1693 } 1694 1695 /* 1696 * Add the correct number of the given character to simulate a tab 1697 * in the field. 1698 */ 1699 static int 1700 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c) 1701 { 1702 int j; 1703 _formi_tab_t *ts = row->tabs; 1704 1705 while ((ts != NULL) && (ts->pos != i)) 1706 ts = ts->fwd; 1707 1708 assert(ts != NULL); 1709 1710 for (j = 0; j < ts->size; j++) 1711 waddch(form->scrwin, c); 1712 1713 return ts->size; 1714 } 1715 1716 1717 /* 1718 * Display the fields attached to the form that are on the current page 1719 * on the screen. 1720 * 1721 */ 1722 int 1723 _formi_draw_page(FORM *form) 1724 { 1725 int i; 1726 1727 if (form->page_starts[form->page].in_use == 0) 1728 return E_BAD_ARGUMENT; 1729 1730 wclear(form->scrwin); 1731 1732 for (i = form->page_starts[form->page].first; 1733 i <= form->page_starts[form->page].last; i++) 1734 _formi_redraw_field(form, i); 1735 1736 return E_OK; 1737 } 1738 1739 /* 1740 * Add the character c at the position pos in buffer 0 of the given field 1741 */ 1742 int 1743 _formi_add_char(FIELD *field, unsigned int pos, char c) 1744 { 1745 char *new, old_c; 1746 unsigned int new_size; 1747 int status; 1748 _FORMI_FIELD_LINES *row, *temp, *next_temp; 1749 1750 row = field->cur_line; 1751 1752 /* 1753 * If buffer has not had a string before, set it to a blank 1754 * string. Everything should flow from there.... 1755 */ 1756 if (row->string == NULL) { 1757 if ((row->string = malloc((size_t)INITIAL_LINE_ALLOC)) == NULL) 1758 return E_SYSTEM_ERROR; 1759 row->string[0] = '\0'; 1760 row->allocated = INITIAL_LINE_ALLOC; 1761 row->length = 0; 1762 row->expanded = 0; 1763 } 1764 1765 if (_formi_validate_char(field, c) != E_OK) { 1766 _formi_dbg_printf("%s: char %c failed char validation\n", 1767 __func__, c); 1768 return E_INVALID_FIELD; 1769 } 1770 1771 if ((c == '\t') && (field->cols <= 8)) { 1772 _formi_dbg_printf("%s: field too small for a tab\n", __func__); 1773 return E_NO_ROOM; 1774 } 1775 1776 _formi_dbg_printf("%s: pos=%d, char=%c\n", __func__, pos, c); 1777 _formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__, 1778 field->cursor_xpos, field->row_xpos, field->start_char); 1779 _formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__, 1780 row->expanded, row->length, row->allocated); 1781 _formi_dbg_printf("%s: %s\n", __func__, row->string); 1782 _formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status); 1783 if (((field->opts & O_BLANK) == O_BLANK) && 1784 (field->buf0_status == FALSE) && 1785 ((field->row_xpos + field->start_char) == 0)) { 1786 row = field->alines; 1787 if (row->next != NULL) { 1788 /* shift all but one line structs to free list */ 1789 temp = row->next; 1790 do { 1791 next_temp = temp->next; 1792 add_to_free(field, temp); 1793 temp = next_temp; 1794 } while (temp != NULL); 1795 } 1796 1797 row->length = 0; 1798 row->string[0] = '\0'; 1799 pos = 0; 1800 field->start_char = 0; 1801 field->start_line = row; 1802 field->cur_line = row; 1803 field->row_count = 1; 1804 field->row_xpos = 0; 1805 field->cursor_ypos = 0; 1806 row->expanded = 0; 1807 row->length = 0; 1808 _formi_init_field_xpos(field); 1809 } 1810 1811 1812 if ((field->overlay == 0) 1813 || ((field->overlay == 1) && (pos >= row->length))) { 1814 /* first check if the field can have more chars...*/ 1815 if (check_field_size(field) == FALSE) 1816 return E_REQUEST_DENIED; 1817 1818 if (row->length + 2 1819 >= row->allocated) { 1820 new_size = row->allocated + 16 - (row->allocated % 16); 1821 if ((new = realloc(row->string, 1822 (size_t) new_size )) == NULL) 1823 return E_SYSTEM_ERROR; 1824 row->allocated = new_size; 1825 row->string = new; 1826 } 1827 } 1828 1829 if ((field->overlay == 0) && (row->length > pos)) { 1830 memmove(&row->string[pos + 1], &row->string[pos], 1831 (size_t) (row->length - pos + 1)); 1832 } 1833 1834 old_c = row->string[pos]; 1835 row->string[pos] = c; 1836 if (pos >= row->length) { 1837 /* make sure the string is terminated if we are at the 1838 * end of the string, the terminator would be missing 1839 * if we are at the end of the field. 1840 */ 1841 row->string[pos + 1] = '\0'; 1842 } 1843 1844 /* only increment the length if we are inserting characters 1845 * OR if we are at the end of the field in overlay mode. 1846 */ 1847 if ((field->overlay == 0) 1848 || ((field->overlay == 1) && (pos >= row->length))) { 1849 row->length++; 1850 } 1851 1852 _formi_calculate_tabs(row); 1853 row->expanded = _formi_tab_expanded_length(row->string, 0, 1854 row->length - 1); 1855 1856 /* wrap the field, if needed */ 1857 status = _formi_wrap_field(field, row); 1858 1859 row = field->cur_line; 1860 pos = field->row_xpos; 1861 1862 /* 1863 * check the wrap worked or that we have not exceeded the 1864 * max field size - this can happen if the field is re-wrapped 1865 * and the row count is increased past the set limit. 1866 */ 1867 if ((status != E_OK) || (check_field_size(field) == FALSE)) { 1868 if ((field->overlay == 0) 1869 || ((field->overlay == 1) 1870 && (pos >= (row->length - 1) /*XXXX- append check???*/))) { 1871 /* 1872 * wrap failed for some reason, back out the 1873 * char insert 1874 */ 1875 memmove(&row->string[pos], &row->string[pos + 1], 1876 (size_t) (row->length - pos)); 1877 row->length--; 1878 if (pos > 0) 1879 pos--; 1880 } else if (field->overlay == 1) { 1881 /* back out character overlay */ 1882 row->string[pos] = old_c; 1883 } 1884 1885 _formi_calculate_tabs(row); 1886 1887 _formi_wrap_field(field, row); 1888 /* 1889 * If we are here then either the status is bad or we 1890 * simply ran out of room. If the status is E_OK then 1891 * we ran out of room, let the form driver know this. 1892 */ 1893 if (status == E_OK) 1894 status = E_REQUEST_DENIED; 1895 1896 } else { 1897 field->buf0_status = TRUE; 1898 field->row_xpos++; 1899 if ((field->rows + field->nrows) == 1) { 1900 status = _formi_set_cursor_xpos(field, FALSE); 1901 } else { 1902 field->cursor_xpos = 1903 _formi_tab_expanded_length( 1904 row->string, 0, field->row_xpos - 1); 1905 1906 /* 1907 * Annoying corner case - if we are right in 1908 * the bottom right corner of the field we 1909 * need to scroll the field one line so the 1910 * cursor is positioned correctly in the 1911 * field. 1912 */ 1913 if ((field->cursor_xpos >= field->cols) && 1914 (field->cursor_ypos == (field->rows - 1))) { 1915 field->cursor_ypos--; 1916 field->start_line = field->start_line->next; 1917 } 1918 } 1919 } 1920 1921 assert((field->cursor_xpos <= field->cols) 1922 && (field->cursor_ypos < 400000)); 1923 1924 _formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__, 1925 field->cursor_xpos, field->row_xpos, field->start_char); 1926 _formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__, 1927 row->expanded, row->length, row->allocated); 1928 _formi_dbg_printf("%s: ypos=%d, start_line=%p\n", __func__, 1929 field->cursor_ypos, field->start_line); 1930 _formi_dbg_printf("%s: %s\n", __func__, row->string); 1931 _formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status); 1932 _formi_dbg_printf("%s: status = %s\n", __func__, 1933 (status == E_OK)? "OK" : "FAILED"); 1934 return status; 1935 } 1936 1937 /* 1938 * Set the position of the cursor on the screen in the row depending on 1939 * where the current position in the string is and the justification 1940 * that is to be applied to the field. Justification is only applied 1941 * to single row, static fields. 1942 */ 1943 static int 1944 _formi_set_cursor_xpos(FIELD *field, int noscroll) 1945 { 1946 int just, pos; 1947 1948 just = field->justification; 1949 pos = field->start_char + field->row_xpos; 1950 1951 _formi_dbg_printf( 1952 "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__, 1953 pos, field->start_char, field->row_xpos, field->cursor_xpos); 1954 1955 /* 1956 * make sure we apply the correct justification to non-static 1957 * fields. 1958 */ 1959 if (((field->rows + field->nrows) != 1) || 1960 ((field->opts & O_STATIC) != O_STATIC)) 1961 just = JUSTIFY_LEFT; 1962 1963 switch (just) { 1964 case JUSTIFY_RIGHT: 1965 field->cursor_xpos = field->cols - 1 1966 - _formi_tab_expanded_length( 1967 field->cur_line->string, 0, 1968 field->cur_line->length - 1) 1969 + _formi_tab_expanded_length( 1970 field->cur_line->string, 0, 1971 field->row_xpos); 1972 break; 1973 1974 case JUSTIFY_CENTER: 1975 field->cursor_xpos = ((field->cols - 1) 1976 - _formi_tab_expanded_length( 1977 field->cur_line->string, 0, 1978 field->cur_line->length - 1) + 1) / 2 1979 + _formi_tab_expanded_length(field->cur_line->string, 1980 0, field->row_xpos); 1981 1982 if (field->cursor_xpos > (field->cols - 1)) 1983 field->cursor_xpos = (field->cols - 1); 1984 break; 1985 1986 default: 1987 field->cursor_xpos = _formi_tab_expanded_length( 1988 field->cur_line->string, 1989 field->start_char, 1990 field->row_xpos + field->start_char); 1991 if ((field->cursor_xpos <= (field->cols - 1)) && 1992 ((field->start_char + field->row_xpos) 1993 < field->cur_line->length)) 1994 field->cursor_xpos--; 1995 1996 if (field->cursor_xpos > (field->cols - 1)) { 1997 if ((field->opts & O_STATIC) == O_STATIC) { 1998 field->start_char = 0; 1999 2000 if (field->row_xpos 2001 == (field->cur_line->length - 1)) { 2002 field->cursor_xpos = field->cols - 1; 2003 } else { 2004 field->cursor_xpos = 2005 _formi_tab_expanded_length( 2006 field->cur_line->string, 2007 field->start_char, 2008 field->row_xpos 2009 + field->start_char 2010 - 1) - 1; 2011 } 2012 } else { 2013 if (noscroll == FALSE) { 2014 field->start_char = 2015 tab_fit_window( 2016 field, 2017 field->start_char 2018 + field->row_xpos, 2019 field->cols); 2020 field->row_xpos = pos 2021 - field->start_char; 2022 field->cursor_xpos = 2023 _formi_tab_expanded_length( 2024 field->cur_line->string, 2025 field->start_char, 2026 field->row_xpos 2027 + field->start_char - 1); 2028 } else { 2029 field->cursor_xpos = (field->cols - 1); 2030 } 2031 } 2032 2033 } 2034 break; 2035 } 2036 2037 _formi_dbg_printf( 2038 "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__, 2039 pos, field->start_char, field->row_xpos, field->cursor_xpos); 2040 return E_OK; 2041 } 2042 2043 /* 2044 * Manipulate the text in a field, this takes the given form and performs 2045 * the passed driver command on the current text field. Returns 1 if the 2046 * text field was modified. 2047 */ 2048 int 2049 _formi_manipulate_field(FORM *form, int c) 2050 { 2051 FIELD *cur; 2052 char *str, saved; 2053 unsigned int start, end, pos, status, old_count, size; 2054 unsigned int old_xpos, old_row_pos; 2055 int len, wb; 2056 bool eat_char; 2057 _FORMI_FIELD_LINES *row, *rs; 2058 2059 cur = form->fields[form->cur_field]; 2060 if (cur->cur_line->string == NULL) 2061 return E_REQUEST_DENIED; 2062 2063 _formi_dbg_printf("%s: request is REQ_%s\n", 2064 __func__, reqs[c - REQ_MIN_REQUEST]); 2065 _formi_dbg_printf( 2066 "%s: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n", 2067 __func__, cur->cursor_xpos, cur->row_xpos, cur->start_char, 2068 cur->cur_line->length, cur->cur_line->allocated); 2069 _formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__, 2070 cur->start_line, cur->cursor_ypos); 2071 if (cur->cur_line->string == NULL) 2072 _formi_dbg_printf("%s: string=(null)\n", __func__); 2073 else 2074 _formi_dbg_printf("%s: string=\"%s\"\n", __func__, 2075 cur->cur_line->string); 2076 2077 /* Cannot manipulate a null string! */ 2078 if (cur->cur_line->string == NULL) 2079 return E_REQUEST_DENIED; 2080 2081 saved = '\0'; 2082 row = cur->cur_line; 2083 2084 switch (c) { 2085 case REQ_RIGHT_CHAR: 2086 /* 2087 * The right_char request performs the same function 2088 * as the next_char request except that the cursor is 2089 * not wrapped if it is at the end of the line, so 2090 * check if the cursor is at the end of the line and 2091 * deny the request otherwise just fall through to 2092 * the next_char request handler. 2093 */ 2094 if (cur->cursor_xpos >= cur->cols - 1) 2095 return E_REQUEST_DENIED; 2096 2097 /* FALLTHRU */ 2098 2099 case REQ_NEXT_CHAR: 2100 /* for a dynamic field allow an offset of one more 2101 * char so we can insert chars after end of string. 2102 * Static fields cannot do this so deny request if 2103 * cursor is at the end of the field. 2104 */ 2105 if (((cur->opts & O_STATIC) == O_STATIC) && 2106 (cur->row_xpos == cur->cols - 1) && 2107 ((cur->rows + cur->nrows) == 1)) 2108 return E_REQUEST_DENIED; 2109 2110 if (((cur->rows + cur->nrows) == 1) && 2111 (cur->row_xpos + cur->start_char + 1) > row->length) 2112 return E_REQUEST_DENIED; 2113 2114 if ((cur->rows + cur->nrows) == 1) { 2115 cur->row_xpos++; 2116 _formi_set_cursor_xpos(cur, (c == REQ_RIGHT_CHAR)); 2117 } else { 2118 if (cur->cursor_xpos >= (row->expanded - 1)) { 2119 if ((row->next == NULL) || 2120 (c == REQ_RIGHT_CHAR)) 2121 return E_REQUEST_DENIED; 2122 2123 cur->cursor_xpos = 0; 2124 cur->row_xpos = 0; 2125 cur->cur_line = cur->cur_line->next; 2126 if (cur->cursor_ypos == (cur->rows - 1)) 2127 cur->start_line = 2128 cur->start_line->next; 2129 else 2130 cur->cursor_ypos++; 2131 } else { 2132 old_xpos = cur->cursor_xpos; 2133 old_row_pos = cur->row_xpos; 2134 if (row->string[cur->row_xpos] == '\t') 2135 cur->cursor_xpos += tab_size(row, 2136 cur->row_xpos); 2137 else 2138 cur->cursor_xpos++; 2139 cur->row_xpos++; 2140 if (cur->cursor_xpos 2141 >= row->expanded) { 2142 if ((row->next == NULL) || 2143 (c == REQ_RIGHT_CHAR)) { 2144 cur->cursor_xpos = old_xpos; 2145 cur->row_xpos = old_row_pos; 2146 return E_REQUEST_DENIED; 2147 } 2148 2149 cur->cursor_xpos = 0; 2150 cur->row_xpos = 0; 2151 cur->cur_line = cur->cur_line->next; 2152 if (cur->cursor_ypos 2153 == (cur->rows - 1)) 2154 cur->start_line = 2155 cur->start_line->next; 2156 else 2157 cur->cursor_ypos++; 2158 } 2159 } 2160 } 2161 2162 break; 2163 2164 case REQ_LEFT_CHAR: 2165 /* 2166 * The behaviour of left_char is the same as prev_char 2167 * except that the cursor will not wrap if it has 2168 * reached the LHS of the field, so just check this 2169 * and fall through if we are not at the LHS. 2170 */ 2171 if (cur->cursor_xpos == 0) 2172 return E_REQUEST_DENIED; 2173 2174 /* FALLTHRU */ 2175 case REQ_PREV_CHAR: 2176 if ((cur->rows + cur->nrows) == 1) { 2177 if (cur->row_xpos == 0) { 2178 if (cur->start_char > 0) 2179 cur->start_char--; 2180 else 2181 return E_REQUEST_DENIED; 2182 } else { 2183 cur->row_xpos--; 2184 _formi_set_cursor_xpos(cur, FALSE); 2185 } 2186 } else { 2187 if ((cur->cursor_xpos == 0) && 2188 (cur->cursor_ypos == 0) && 2189 (cur->start_line->prev == NULL)) 2190 return E_REQUEST_DENIED; 2191 2192 pos = cur->row_xpos; 2193 if (cur->cursor_xpos > 0) { 2194 if (row->string[pos] == '\t') { 2195 size = tab_size(row, pos); 2196 if (size > cur->cursor_xpos) { 2197 cur->cursor_xpos = 0; 2198 cur->row_xpos = 0; 2199 } else { 2200 cur->row_xpos--; 2201 cur->cursor_xpos -= size; 2202 } 2203 } else { 2204 cur->cursor_xpos--; 2205 cur->row_xpos--; 2206 } 2207 } else { 2208 cur->cur_line = cur->cur_line->prev; 2209 if (cur->cursor_ypos > 0) 2210 cur->cursor_ypos--; 2211 else 2212 cur->start_line = 2213 cur->start_line->prev; 2214 row = cur->cur_line; 2215 if (row->expanded > 0) { 2216 cur->cursor_xpos = row->expanded - 1; 2217 } else { 2218 cur->cursor_xpos = 0; 2219 } 2220 2221 if (row->length > 0) 2222 cur->row_xpos = row->length - 1; 2223 else 2224 cur->row_xpos = 0; 2225 } 2226 } 2227 2228 break; 2229 2230 case REQ_DOWN_CHAR: 2231 /* 2232 * The down_char request has the same functionality as 2233 * the next_line request excepting that the field is not 2234 * scrolled if the cursor is at the bottom of the field. 2235 * Check to see if the cursor is at the bottom of the field 2236 * and if it is then deny the request otherwise fall 2237 * through to the next_line handler. 2238 */ 2239 if (cur->cursor_ypos >= cur->rows - 1) 2240 return E_REQUEST_DENIED; 2241 2242 /* FALLTHRU */ 2243 2244 case REQ_NEXT_LINE: 2245 if ((row->next == NULL) || (cur->cur_line->next == NULL)) 2246 return E_REQUEST_DENIED; 2247 2248 cur->cur_line = cur->cur_line->next; 2249 if ((cur->cursor_ypos + 1) >= cur->rows) { 2250 cur->start_line = cur->start_line->next; 2251 } else 2252 cur->cursor_ypos++; 2253 row = cur->cur_line; 2254 2255 if (row->length == 0) { 2256 cur->row_xpos = 0; 2257 cur->cursor_xpos = 0; 2258 } else { 2259 if (cur->cursor_xpos > (row->expanded - 1)) 2260 cur->cursor_xpos = row->expanded - 1; 2261 2262 cur->row_xpos = tab_fit_len(row, cur->cursor_xpos + 1); 2263 if (cur->row_xpos == 0) 2264 cur->cursor_xpos = 0; 2265 else 2266 cur->cursor_xpos = 2267 _formi_tab_expanded_length( 2268 row->string, 0, cur->row_xpos); 2269 if (cur->cursor_xpos > 0) 2270 cur->cursor_xpos--; 2271 } 2272 break; 2273 2274 case REQ_UP_CHAR: 2275 /* 2276 * The up_char request has the same functionality as 2277 * the prev_line request excepting the field is not 2278 * scrolled, check if the cursor is at the top of the 2279 * field, if it is deny the request otherwise fall 2280 * through to the prev_line handler. 2281 */ 2282 if (cur->cursor_ypos == 0) 2283 return E_REQUEST_DENIED; 2284 2285 /* FALLTHRU */ 2286 2287 case REQ_PREV_LINE: 2288 if (cur->cur_line->prev == NULL) 2289 return E_REQUEST_DENIED; 2290 2291 if (cur->cursor_ypos == 0) { 2292 if (cur->start_line->prev == NULL) 2293 return E_REQUEST_DENIED; 2294 cur->start_line = cur->start_line->prev; 2295 } else 2296 cur->cursor_ypos--; 2297 2298 cur->cur_line = cur->cur_line->prev; 2299 row = cur->cur_line; 2300 2301 if (row->length == 0) { 2302 cur->row_xpos = 0; 2303 cur->cursor_xpos = 0; 2304 } else { 2305 if (cur->cursor_xpos > (row->expanded - 1)) 2306 cur->cursor_xpos = row->expanded - 1; 2307 2308 cur->row_xpos = tab_fit_len(row, cur->cursor_xpos + 1); 2309 cur->cursor_xpos = 2310 _formi_tab_expanded_length(row->string, 2311 0, cur->row_xpos); 2312 if (cur->cursor_xpos > 0) 2313 cur->cursor_xpos--; 2314 } 2315 break; 2316 2317 case REQ_NEXT_WORD: 2318 start = cur->row_xpos + cur->start_char; 2319 str = row->string; 2320 2321 wb = find_eow(cur, start, FALSE, &row); 2322 if (wb < 0) 2323 return wb; 2324 2325 start = wb; 2326 /* check if we hit the end */ 2327 if (str[start] == '\0') 2328 return E_REQUEST_DENIED; 2329 2330 /* otherwise we must have found the start of a word...*/ 2331 if ((cur->rows + cur->nrows) == 1) { 2332 /* single line field */ 2333 size = _formi_tab_expanded_length(str, 2334 cur->start_char, start); 2335 if (size < cur->cols) { 2336 cur->row_xpos = start - cur->start_char; 2337 } else { 2338 cur->start_char = start; 2339 cur->row_xpos = 0; 2340 } 2341 _formi_set_cursor_xpos(cur, FALSE); 2342 } else { 2343 /* multiline field */ 2344 cur->cur_line = row; 2345 adjust_ypos(cur, row); 2346 2347 cur->row_xpos = start; 2348 cur->cursor_xpos = 2349 _formi_tab_expanded_length( 2350 row->string, 0, cur->row_xpos) - 1; 2351 } 2352 break; 2353 2354 case REQ_PREV_WORD: 2355 start = cur->start_char + cur->row_xpos; 2356 if (cur->start_char > 0) 2357 start--; 2358 2359 if ((start == 0) && (row->prev == NULL)) 2360 return E_REQUEST_DENIED; 2361 2362 if (start == 0) { 2363 row = row->prev; 2364 if (row->length > 0) 2365 start = row->length - 1; 2366 else 2367 start = 0; 2368 } 2369 2370 str = row->string; 2371 2372 start = find_sow(start, &row); 2373 2374 if ((cur->rows + cur->nrows) == 1) { 2375 /* single line field */ 2376 size = _formi_tab_expanded_length(str, 2377 cur->start_char, start); 2378 2379 if (start > cur->start_char) { 2380 cur->row_xpos = start - cur->start_char; 2381 } else { 2382 cur->start_char = start; 2383 cur->row_xpos = 0; 2384 } 2385 _formi_set_cursor_xpos(cur, FALSE); 2386 } else { 2387 /* multiline field */ 2388 cur->cur_line = row; 2389 adjust_ypos(cur, row); 2390 cur->row_xpos = start; 2391 cur->cursor_xpos = 2392 _formi_tab_expanded_length( 2393 row->string, 0, 2394 cur->row_xpos) - 1; 2395 } 2396 2397 break; 2398 2399 case REQ_BEG_FIELD: 2400 cur->start_char = 0; 2401 while (cur->start_line->prev != NULL) 2402 cur->start_line = cur->start_line->prev; 2403 cur->cur_line = cur->start_line; 2404 cur->row_xpos = 0; 2405 _formi_init_field_xpos(cur); 2406 cur->cursor_ypos = 0; 2407 break; 2408 2409 case REQ_BEG_LINE: 2410 cur->row_xpos = 0; 2411 _formi_init_field_xpos(cur); 2412 cur->start_char = 0; 2413 break; 2414 2415 case REQ_END_FIELD: 2416 while (cur->cur_line->next != NULL) 2417 cur->cur_line = cur->cur_line->next; 2418 2419 if (cur->row_count > cur->rows) { 2420 cur->start_line = cur->cur_line; 2421 pos = cur->rows - 1; 2422 while (pos > 0) { 2423 cur->start_line = cur->start_line->prev; 2424 pos--; 2425 } 2426 cur->cursor_ypos = cur->rows - 1; 2427 } else { 2428 cur->cursor_ypos = cur->row_count - 1; 2429 } 2430 2431 /* we fall through here deliberately, we are on the 2432 * correct row, now we need to get to the end of the 2433 * line. 2434 */ 2435 /* FALLTHRU */ 2436 2437 case REQ_END_LINE: 2438 row = cur->cur_line; 2439 2440 if ((cur->rows + cur->nrows) == 1) { 2441 if (row->expanded > cur->cols - 1) { 2442 if ((cur->opts & O_STATIC) != O_STATIC) { 2443 cur->start_char = tab_fit_window( 2444 cur, row->length, 2445 cur->cols) + 1; 2446 cur->row_xpos = row->length 2447 - cur->start_char; 2448 } else { 2449 cur->start_char = 0; 2450 cur->row_xpos = cur->cols - 1; 2451 } 2452 } else { 2453 cur->row_xpos = row->length + 1; 2454 cur->start_char = 0; 2455 } 2456 _formi_set_cursor_xpos(cur, FALSE); 2457 } else { 2458 cur->row_xpos = row->length - 1; 2459 cur->cursor_xpos = row->expanded - 1; 2460 if (row->next == NULL) { 2461 cur->row_xpos++; 2462 cur->cursor_xpos++; 2463 } 2464 } 2465 break; 2466 2467 case REQ_NEW_LINE: 2468 start = cur->start_char + cur->row_xpos; 2469 if ((status = split_line(cur, TRUE, start, &row)) != E_OK) 2470 return status; 2471 cur->cur_line->hard_ret = TRUE; 2472 cur->cursor_xpos = 0; 2473 cur->row_xpos = 0; 2474 break; 2475 2476 case REQ_INS_CHAR: 2477 if ((status = _formi_add_char(cur, cur->start_char 2478 + cur->row_xpos, 2479 cur->pad)) != E_OK) 2480 return status; 2481 break; 2482 2483 case REQ_INS_LINE: 2484 if ((status = split_line(cur, TRUE, 0, &row)) != E_OK) 2485 return status; 2486 cur->cur_line->hard_ret = TRUE; 2487 break; 2488 2489 case REQ_DEL_CHAR: 2490 row = cur->cur_line; 2491 start = cur->start_char + cur->row_xpos; 2492 end = row->length - 1; 2493 if ((start >= row->length) && (row->next == NULL)) 2494 return E_REQUEST_DENIED; 2495 2496 if ((start == row->length - 1) || (row->length == 0)) { 2497 if ((cur->rows + cur->nrows) > 1) { 2498 /* 2499 * Firstly, check if the current line has 2500 * a hard return. In this case we just 2501 * want to "delete" the hard return and 2502 * re-wrap the field. The hard return 2503 * does not occupy a character space in 2504 * the buffer but we must make it appear 2505 * like it does for a deletion. 2506 */ 2507 if (row->hard_ret == TRUE) { 2508 row->hard_ret = FALSE; 2509 if (_formi_join_line(cur, &row, 2510 JOIN_NEXT) 2511 != E_OK) { 2512 row->hard_ret = TRUE; 2513 return 0; 2514 } else { 2515 return 1; 2516 } 2517 } 2518 2519 /* 2520 * If we have more than one row, join the 2521 * next row to make things easier unless 2522 * we are at the end of the string, in 2523 * that case the join would fail but we 2524 * really want to delete the last char 2525 * in the field. 2526 */ 2527 if (row->next != NULL) { 2528 if (_formi_join_line(cur, &row, 2529 JOIN_NEXT_NW) 2530 != E_OK) { 2531 return E_REQUEST_DENIED; 2532 } 2533 } 2534 } 2535 } 2536 2537 saved = row->string[start]; 2538 memmove(&row->string[start], &row->string[start + 1], 2539 (size_t) (end - start + 1)); 2540 row->string[end] = '\0'; 2541 row->length--; 2542 if (row->length > 0) 2543 row->expanded = _formi_tab_expanded_length( 2544 row->string, 0, row->length - 1); 2545 else 2546 row->expanded = 0; 2547 2548 /* 2549 * recalculate tabs for a single line field, multiline 2550 * fields will do this when the field is wrapped. 2551 */ 2552 if ((cur->rows + cur->nrows) == 1) 2553 _formi_calculate_tabs(row); 2554 /* 2555 * if we are at the end of the string then back the 2556 * cursor pos up one to stick on the end of the line 2557 */ 2558 if (start == row->length) { 2559 if (row->length > 1) { 2560 if ((cur->rows + cur->nrows) == 1) { 2561 pos = cur->row_xpos + cur->start_char; 2562 cur->start_char = 2563 tab_fit_window( 2564 cur, 2565 cur->start_char + cur->row_xpos, 2566 cur->cols); 2567 cur->row_xpos = pos - cur->start_char 2568 - 1; 2569 _formi_set_cursor_xpos(cur, FALSE); 2570 } else { 2571 if (cur->row_xpos == 0) { 2572 if (row->next != NULL) { 2573 if (_formi_join_line( 2574 cur, &row, 2575 JOIN_PREV_NW) 2576 != E_OK) { 2577 return E_REQUEST_DENIED; 2578 } 2579 } else { 2580 if (cur->row_count > 1) 2581 cur->row_count--; 2582 } 2583 2584 } 2585 2586 cur->row_xpos = start - 1; 2587 cur->cursor_xpos = 2588 _formi_tab_expanded_length( 2589 row->string, 2590 0, cur->row_xpos - 1); 2591 if ((cur->cursor_xpos > 0) 2592 && (start != (row->expanded - 1))) 2593 cur->cursor_xpos--; 2594 } 2595 2596 start--; 2597 } else { 2598 start = 0; 2599 cur->row_xpos = 0; 2600 _formi_init_field_xpos(cur); 2601 } 2602 } 2603 2604 if ((cur->rows + cur->nrows) > 1) { 2605 if (_formi_wrap_field(cur, row) != E_OK) { 2606 memmove(&row->string[start + 1], 2607 &row->string[start], 2608 (size_t) (end - start)); 2609 row->length++; 2610 row->string[start] = saved; 2611 _formi_wrap_field(cur, row); 2612 return E_REQUEST_DENIED; 2613 } 2614 } 2615 break; 2616 2617 case REQ_DEL_PREV: 2618 if ((cur->cursor_xpos == 0) && (cur->start_char == 0) 2619 && (cur->start_line->prev == NULL) 2620 && (cur->cursor_ypos == 0)) 2621 return E_REQUEST_DENIED; 2622 2623 row = cur->cur_line; 2624 start = cur->row_xpos + cur->start_char; 2625 end = row->length - 1; 2626 eat_char = TRUE; 2627 2628 if ((cur->start_char + cur->row_xpos) == 0) { 2629 if (row->prev == NULL) 2630 return E_REQUEST_DENIED; 2631 2632 /* 2633 * If we are a multiline field then check if 2634 * the line above has a hard return. If it does 2635 * then just "eat" the hard return and re-wrap 2636 * the field. 2637 */ 2638 if (row->prev->hard_ret == TRUE) { 2639 row->prev->hard_ret = FALSE; 2640 if (_formi_join_line(cur, &row, 2641 JOIN_PREV) != E_OK) { 2642 row->prev->hard_ret = TRUE; 2643 return 0; 2644 } 2645 2646 eat_char = FALSE; 2647 } else { 2648 start = row->prev->length; 2649 /* 2650 * Join this line to the previous 2651 * one. 2652 */ 2653 if (_formi_join_line(cur, &row, 2654 JOIN_PREV_NW) != E_OK) { 2655 return 0; 2656 } 2657 end = row->length - 1; 2658 } 2659 } 2660 2661 if (eat_char == TRUE) { 2662 /* 2663 * eat a char from the buffer. Normally we do 2664 * this unless we have deleted a "hard return" 2665 * in which case we just want to join the lines 2666 * without losing a char. 2667 */ 2668 saved = row->string[start - 1]; 2669 memmove(&row->string[start - 1], &row->string[start], 2670 (size_t) (end - start + 1)); 2671 row->length--; 2672 row->string[row->length] = '\0'; 2673 row->expanded = _formi_tab_expanded_length( 2674 row->string, 0, row->length - 1); 2675 } 2676 2677 if ((cur->rows + cur->nrows) == 1) { 2678 _formi_calculate_tabs(row); 2679 pos = cur->row_xpos + cur->start_char; 2680 if (pos > 0) 2681 pos--; 2682 cur->start_char = 2683 tab_fit_window(cur, 2684 cur->start_char + cur->row_xpos, 2685 cur->cols); 2686 cur->row_xpos = pos - cur->start_char; 2687 _formi_set_cursor_xpos(cur, FALSE); 2688 } else { 2689 if (eat_char == TRUE) { 2690 cur->row_xpos--; 2691 if (cur->row_xpos > 0) 2692 cur->cursor_xpos = 2693 _formi_tab_expanded_length( 2694 row->string, 0, 2695 cur->row_xpos - 1); 2696 else 2697 cur->cursor_xpos = 0; 2698 } 2699 2700 if ((_formi_wrap_field(cur, row) != E_OK)) { 2701 memmove(&row->string[start], 2702 &row->string[start - 1], 2703 (size_t) (end - start)); 2704 row->length++; 2705 row->string[start - 1] = saved; 2706 row->string[row->length] = '\0'; 2707 _formi_wrap_field(cur, row); 2708 return E_REQUEST_DENIED; 2709 } 2710 } 2711 break; 2712 2713 case REQ_DEL_LINE: 2714 if (((cur->rows + cur->nrows) == 1) || 2715 (cur->row_count == 1)) { 2716 /* single line case */ 2717 row->length = 0; 2718 row->expanded = row->length = 0; 2719 cur->row_xpos = 0; 2720 _formi_init_field_xpos(cur); 2721 cur->cursor_ypos = 0; 2722 } else { 2723 /* multiline field */ 2724 old_count = cur->row_count; 2725 cur->row_count--; 2726 if (cur->row_count == 0) 2727 cur->row_count = 1; 2728 2729 if (old_count == 1) { 2730 row->expanded = row->length = 0; 2731 cur->cursor_xpos = 0; 2732 cur->row_xpos = 0; 2733 cur->cursor_ypos = 0; 2734 } else 2735 add_to_free(cur, row); 2736 2737 if (row->next == NULL) { 2738 if (cur->cursor_ypos == 0) { 2739 if (cur->start_line->prev != NULL) { 2740 cur->start_line = 2741 cur->start_line->prev; 2742 } 2743 } else { 2744 cur->cursor_ypos--; 2745 } 2746 } 2747 2748 if (old_count > 1) { 2749 if (cur->cursor_xpos > row->expanded) { 2750 cur->cursor_xpos = row->expanded - 1; 2751 cur->row_xpos = row->length - 1; 2752 } 2753 2754 cur->start_line = cur->alines; 2755 rs = cur->start_line; 2756 cur->cursor_ypos = 0; 2757 while (rs != row) { 2758 if (cur->cursor_ypos < cur->rows) 2759 cur->cursor_ypos++; 2760 else 2761 cur->start_line = 2762 cur->start_line->next; 2763 rs = rs->next; 2764 } 2765 } 2766 } 2767 break; 2768 2769 case REQ_DEL_WORD: 2770 start = cur->start_char + cur->row_xpos; 2771 str = row->string; 2772 2773 wb = find_eow(cur, start, TRUE, &row); 2774 if (wb < 0) 2775 return wb; 2776 2777 end = wb; 2778 2779 /* 2780 * If not at the start of a word then find the start, 2781 * we cannot blindly call find_sow because this will 2782 * skip back a word if we are already at the start of 2783 * a word. 2784 */ 2785 if ((start > 0) 2786 && !(isblank((unsigned char)str[start - 1]) && 2787 !isblank((unsigned char)str[start]))) 2788 start = find_sow(start, &row); 2789 str = row->string; 2790 /* XXXX hmmmm what if start and end on diff rows? XXXX */ 2791 memmove(&str[start], &str[end], 2792 (size_t) (row->length - end + 1)); 2793 len = end - start; 2794 row->length -= len; 2795 2796 if ((cur->rows + cur->nrows) > 1) { 2797 row = cur->start_line + cur->cursor_ypos; 2798 if (row->next != NULL) { 2799 /* 2800 * if not on the last row we need to 2801 * join on the next row so the line 2802 * will be re-wrapped. 2803 */ 2804 _formi_join_line(cur, &row, JOIN_NEXT_NW); 2805 } 2806 _formi_wrap_field(cur, row); 2807 cur->row_xpos = start; 2808 cur->cursor_xpos = _formi_tab_expanded_length( 2809 row->string, 0, cur->row_xpos); 2810 if (cur->cursor_xpos > 0) 2811 cur->cursor_xpos--; 2812 } else { 2813 _formi_calculate_tabs(row); 2814 cur->row_xpos = start - cur->start_char; 2815 if (cur->row_xpos > 0) 2816 cur->row_xpos--; 2817 _formi_set_cursor_xpos(cur, FALSE); 2818 } 2819 break; 2820 2821 case REQ_CLR_EOL: 2822 row->string[cur->row_xpos + 1] = '\0'; 2823 row->length = cur->row_xpos + 1; 2824 row->expanded = cur->cursor_xpos + 1; 2825 break; 2826 2827 case REQ_CLR_EOF: 2828 row = cur->cur_line->next; 2829 while (row != NULL) { 2830 rs = row->next; 2831 add_to_free(cur, row); 2832 row = rs; 2833 cur->row_count--; 2834 } 2835 break; 2836 2837 case REQ_CLR_FIELD: 2838 row = cur->alines->next; 2839 cur->cur_line = cur->alines; 2840 cur->start_line = cur->alines; 2841 2842 while (row != NULL) { 2843 rs = row->next; 2844 add_to_free(cur, row); 2845 row = rs; 2846 } 2847 2848 cur->alines->string[0] = '\0'; 2849 cur->alines->length = 0; 2850 cur->alines->expanded = 0; 2851 cur->row_count = 1; 2852 cur->cursor_ypos = 0; 2853 cur->row_xpos = 0; 2854 _formi_init_field_xpos(cur); 2855 cur->start_char = 0; 2856 break; 2857 2858 case REQ_OVL_MODE: 2859 cur->overlay = 1; 2860 break; 2861 2862 case REQ_INS_MODE: 2863 cur->overlay = 0; 2864 break; 2865 2866 case REQ_SCR_FLINE: 2867 _formi_scroll_fwd(cur, 1); 2868 break; 2869 2870 case REQ_SCR_BLINE: 2871 _formi_scroll_back(cur, 1); 2872 break; 2873 2874 case REQ_SCR_FPAGE: 2875 _formi_scroll_fwd(cur, cur->rows); 2876 break; 2877 2878 case REQ_SCR_BPAGE: 2879 _formi_scroll_back(cur, cur->rows); 2880 break; 2881 2882 case REQ_SCR_FHPAGE: 2883 _formi_scroll_fwd(cur, cur->rows / 2); 2884 break; 2885 2886 case REQ_SCR_BHPAGE: 2887 _formi_scroll_back(cur, cur->rows / 2); 2888 break; 2889 2890 case REQ_SCR_FCHAR: 2891 _formi_hscroll_fwd(cur, row, 1); 2892 break; 2893 2894 case REQ_SCR_BCHAR: 2895 _formi_hscroll_back(cur, row, 1); 2896 break; 2897 2898 case REQ_SCR_HFLINE: 2899 _formi_hscroll_fwd(cur, row, cur->cols); 2900 break; 2901 2902 case REQ_SCR_HBLINE: 2903 _formi_hscroll_back(cur, row, cur->cols); 2904 break; 2905 2906 case REQ_SCR_HFHALF: 2907 _formi_hscroll_fwd(cur, row, cur->cols / 2); 2908 break; 2909 2910 case REQ_SCR_HBHALF: 2911 _formi_hscroll_back(cur, row, cur->cols / 2); 2912 break; 2913 2914 default: 2915 return 0; 2916 } 2917 2918 _formi_dbg_printf( 2919 "%s: cursor_xpos=%d, row_xpos=%d, start_char=%d, length=%d, " 2920 "allocated=%d\n", __func__, cur->cursor_xpos, cur->row_xpos, 2921 cur->start_char, cur->cur_line->length, cur->cur_line->allocated); 2922 _formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__, 2923 cur->start_line, cur->cursor_ypos); 2924 _formi_dbg_printf("%s: string=\"%s\"\n", __func__, 2925 cur->cur_line->string); 2926 assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX) 2927 && (cur->cursor_xpos >= cur->row_xpos)); 2928 return 1; 2929 } 2930 2931 /* 2932 * Validate the given character by passing it to any type character 2933 * checking routines, if they exist. 2934 */ 2935 int 2936 _formi_validate_char(FIELD *field, char c) 2937 { 2938 int ret_val; 2939 2940 if (field->type == NULL) 2941 return E_OK; 2942 2943 ret_val = E_INVALID_FIELD; 2944 _formi_do_char_validation(field, field->type, c, &ret_val); 2945 2946 return ret_val; 2947 } 2948 2949 2950 /* 2951 * Perform the validation of the character, invoke all field_type validation 2952 * routines. If the field is ok then update ret_val to E_OK otherwise 2953 * ret_val is not changed. 2954 */ 2955 static void 2956 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val) 2957 { 2958 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) { 2959 _formi_do_char_validation(field, type->link->next, c, ret_val); 2960 _formi_do_char_validation(field, type->link->prev, c, ret_val); 2961 } else { 2962 if (type->char_check == NULL) 2963 *ret_val = E_OK; 2964 else { 2965 if (type->char_check((int)(unsigned char) c, 2966 field->args) == TRUE) 2967 *ret_val = E_OK; 2968 } 2969 } 2970 } 2971 2972 /* 2973 * Validate the current field. If the field validation returns success then 2974 * return E_OK otherwise return E_INVALID_FIELD. 2975 * 2976 */ 2977 int 2978 _formi_validate_field(FORM *form) 2979 { 2980 FIELD *cur; 2981 int ret_val, count; 2982 2983 2984 if ((form == NULL) || (form->fields == NULL) || 2985 (form->fields[0] == NULL)) 2986 return E_INVALID_FIELD; 2987 2988 cur = form->fields[form->cur_field]; 2989 2990 /* 2991 * Sync the buffer if it has been modified so the field 2992 * validation routines can use it and because this is 2993 * the correct behaviour according to AT&T implementation. 2994 */ 2995 if ((cur->buf0_status == TRUE) 2996 && ((ret_val = _formi_sync_buffer(cur)) != E_OK)) 2997 return ret_val; 2998 2999 /* 3000 * If buffer is untouched then the string pointer may be 3001 * NULL, see if this is ok or not. 3002 */ 3003 if (cur->buffers[0].string == NULL) { 3004 if ((cur->opts & O_NULLOK) == O_NULLOK) 3005 return E_OK; 3006 else 3007 return E_INVALID_FIELD; 3008 } 3009 3010 count = _formi_skip_blanks(cur->buffers[0].string, 0); 3011 3012 /* check if we have a null field, depending on the nullok flag 3013 * this may be acceptable or not.... 3014 */ 3015 if (cur->buffers[0].string[count] == '\0') { 3016 if ((cur->opts & O_NULLOK) == O_NULLOK) 3017 return E_OK; 3018 else 3019 return E_INVALID_FIELD; 3020 } 3021 3022 /* check if an unmodified field is ok */ 3023 if (cur->buf0_status == 0) { 3024 if ((cur->opts & O_PASSOK) == O_PASSOK) 3025 return E_OK; 3026 else 3027 return E_INVALID_FIELD; 3028 } 3029 3030 /* if there is no type then just accept the field */ 3031 if (cur->type == NULL) 3032 return E_OK; 3033 3034 ret_val = E_INVALID_FIELD; 3035 _formi_do_validation(cur, cur->type, &ret_val); 3036 3037 return ret_val; 3038 } 3039 3040 /* 3041 * Perform the validation of the field, invoke all field_type validation 3042 * routines. If the field is ok then update ret_val to E_OK otherwise 3043 * ret_val is not changed. 3044 */ 3045 static void 3046 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val) 3047 { 3048 if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) { 3049 _formi_do_validation(field, type->link->next, ret_val); 3050 _formi_do_validation(field, type->link->prev, ret_val); 3051 } else { 3052 if (type->field_check == NULL) 3053 *ret_val = E_OK; 3054 else { 3055 if (type->field_check(field, field_buffer(field, 0)) 3056 == TRUE) 3057 *ret_val = E_OK; 3058 } 3059 } 3060 } 3061 3062 /* 3063 * Select the next/previous choice for the field, the driver command 3064 * selecting the direction will be passed in c. Return 1 if a choice 3065 * selection succeeded, 0 otherwise. 3066 */ 3067 int 3068 _formi_field_choice(FORM *form, int c) 3069 { 3070 FIELDTYPE *type; 3071 FIELD *field; 3072 3073 if ((form == NULL) || (form->fields == NULL) || 3074 (form->fields[0] == NULL) || 3075 (form->fields[form->cur_field]->type == NULL)) 3076 return 0; 3077 3078 field = form->fields[form->cur_field]; 3079 type = field->type; 3080 3081 switch (c) { 3082 case REQ_NEXT_CHOICE: 3083 if (type->next_choice == NULL) 3084 return 0; 3085 else 3086 return type->next_choice(field, 3087 field_buffer(field, 0)); 3088 3089 case REQ_PREV_CHOICE: 3090 if (type->prev_choice == NULL) 3091 return 0; 3092 else 3093 return type->prev_choice(field, 3094 field_buffer(field, 0)); 3095 3096 default: /* should never happen! */ 3097 return 0; 3098 } 3099 } 3100 3101 /* 3102 * Update the fields if they have changed. The parameter old has the 3103 * previous current field as the current field may have been updated by 3104 * the driver. Return 1 if the form page needs updating. 3105 * 3106 */ 3107 int 3108 _formi_update_field(FORM *form, int old_field) 3109 { 3110 int cur, i; 3111 3112 cur = form->cur_field; 3113 3114 if (old_field != cur) { 3115 if (!((cur >= form->page_starts[form->page].first) && 3116 (cur <= form->page_starts[form->page].last))) { 3117 /* not on same page any more */ 3118 for (i = 0; i < form->max_page; i++) { 3119 if ((form->page_starts[i].in_use == 1) && 3120 (form->page_starts[i].first <= cur) && 3121 (form->page_starts[i].last >= cur)) { 3122 form->page = i; 3123 return 1; 3124 } 3125 } 3126 } 3127 } 3128 3129 _formi_redraw_field(form, old_field); 3130 _formi_redraw_field(form, form->cur_field); 3131 return 0; 3132 } 3133 3134 /* 3135 * Compare function for the field sorting 3136 * 3137 */ 3138 static int 3139 field_sort_compare(const void *one, const void *two) 3140 { 3141 const FIELD *a, *b; 3142 int tl; 3143 3144 a = *(const FIELD **) __UNCONST(one); 3145 b = *(const FIELD **) __UNCONST(two); 3146 3147 if (a == NULL) 3148 return 1; 3149 3150 if (b == NULL) 3151 return -1; 3152 3153 /* 3154 * First check the page, we want the fields sorted by page. 3155 * 3156 */ 3157 if (a->page != b->page) 3158 return ((a->page > b->page)? 1 : -1); 3159 3160 tl = _formi_top_left(a->parent, a->index, b->index); 3161 3162 /* 3163 * sort fields left to right, top to bottom so the top left is 3164 * the lesser value.... 3165 */ 3166 return ((tl == a->index)? -1 : 1); 3167 } 3168 3169 /* 3170 * Sort the fields in a form ready for driver traversal. 3171 */ 3172 void 3173 _formi_sort_fields(FORM *form) 3174 { 3175 FIELD **sort_area; 3176 int i; 3177 3178 TAILQ_INIT(&form->sorted_fields); 3179 3180 if ((sort_area = malloc(sizeof(*sort_area) * form->field_count)) 3181 == NULL) 3182 return; 3183 3184 memcpy(sort_area, form->fields, sizeof(*sort_area) * form->field_count); 3185 qsort(sort_area, (size_t) form->field_count, sizeof(FIELD *), 3186 field_sort_compare); 3187 3188 for (i = 0; i < form->field_count; i++) 3189 TAILQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue); 3190 3191 free(sort_area); 3192 } 3193 3194 /* 3195 * Set the neighbours for all the fields in the given form. 3196 */ 3197 void 3198 _formi_stitch_fields(FORM *form) 3199 { 3200 int above_row, below_row, end_above, end_below, cur_row, real_end; 3201 FIELD *cur, *above, *below; 3202 3203 /* 3204 * check if the sorted fields circle queue is empty, just 3205 * return if it is. 3206 */ 3207 if (TAILQ_EMPTY(&form->sorted_fields)) 3208 return; 3209 3210 /* initially nothing is above..... */ 3211 above_row = -1; 3212 end_above = TRUE; 3213 above = NULL; 3214 3215 /* set up the first field as the current... */ 3216 cur = TAILQ_FIRST(&form->sorted_fields); 3217 cur_row = cur->form_row; 3218 3219 /* find the first field on the next row if any */ 3220 below = TAILQ_NEXT(cur, glue); 3221 below_row = -1; 3222 end_below = TRUE; 3223 real_end = TRUE; 3224 while (below != NULL) { 3225 if (below->form_row != cur_row) { 3226 below_row = below->form_row; 3227 end_below = FALSE; 3228 real_end = FALSE; 3229 break; 3230 } 3231 below = TAILQ_NEXT(below, glue); 3232 } 3233 3234 /* walk the sorted fields, setting the neighbour pointers */ 3235 while (cur != NULL) { 3236 if (cur == TAILQ_FIRST(&form->sorted_fields)) 3237 cur->left = NULL; 3238 else 3239 cur->left = TAILQ_PREV(cur, _formi_sort_head, glue); 3240 3241 if (cur == TAILQ_LAST(&form->sorted_fields, _formi_sort_head)) 3242 cur->right = NULL; 3243 else 3244 cur->right = TAILQ_NEXT(cur, glue); 3245 3246 if (end_above == TRUE) 3247 cur->up = NULL; 3248 else { 3249 cur->up = above; 3250 above = TAILQ_NEXT(above, glue); 3251 if (above_row != above->form_row) { 3252 end_above = TRUE; 3253 above_row = above->form_row; 3254 } 3255 } 3256 3257 if (end_below == TRUE) 3258 cur->down = NULL; 3259 else { 3260 cur->down = below; 3261 below = TAILQ_NEXT(below, glue); 3262 if (below == NULL) { 3263 end_below = TRUE; 3264 real_end = TRUE; 3265 } else if (below_row != below->form_row) { 3266 end_below = TRUE; 3267 below_row = below->form_row; 3268 } 3269 } 3270 3271 cur = TAILQ_NEXT(cur, glue); 3272 if ((cur != NULL) 3273 && (cur_row != cur->form_row)) { 3274 cur_row = cur->form_row; 3275 if (end_above == FALSE) { 3276 for (; above != 3277 TAILQ_FIRST(&form->sorted_fields); 3278 above = TAILQ_NEXT(above, glue)) { 3279 if (above->form_row != above_row) { 3280 above_row = above->form_row; 3281 break; 3282 } 3283 } 3284 } else if (above == NULL) { 3285 above = TAILQ_FIRST(&form->sorted_fields); 3286 end_above = FALSE; 3287 above_row = above->form_row; 3288 } else 3289 end_above = FALSE; 3290 3291 if (end_below == FALSE) { 3292 while (below_row == below->form_row) { 3293 below = TAILQ_NEXT(below, glue); 3294 if (below == NULL) { 3295 real_end = TRUE; 3296 end_below = TRUE; 3297 break; 3298 } 3299 } 3300 3301 if (below != NULL) 3302 below_row = below->form_row; 3303 } else if (real_end == FALSE) 3304 end_below = FALSE; 3305 3306 } 3307 } 3308 } 3309 3310 /* 3311 * Calculate the length of the displayed line allowing for any tab 3312 * characters that need to be expanded. We assume that the tab stops 3313 * are 8 characters apart. The parameters start and end are the 3314 * character positions in the string str we want to get the length of, 3315 * the function returns the number of characters from the start 3316 * position to the end position that should be displayed after any 3317 * intervening tabs have been expanded. 3318 */ 3319 int 3320 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end) 3321 { 3322 int len, start_len, i; 3323 3324 /* if we have a null string then there is no length */ 3325 if (str[0] == '\0') 3326 return 0; 3327 3328 len = 0; 3329 start_len = 0; 3330 3331 /* 3332 * preceding tabs affect the length tabs in the span, so 3333 * we need to calculate the length including the stuff before 3334 * start and then subtract off the unwanted bit. 3335 */ 3336 for (i = 0; i <= end; i++) { 3337 if (i == start) /* stash preamble length for later */ 3338 start_len = len; 3339 3340 if (str[i] == '\0') 3341 break; 3342 3343 if (str[i] == '\t') 3344 len = len - (len % 8) + 8; 3345 else 3346 len++; 3347 } 3348 3349 _formi_dbg_printf( 3350 "%s: start=%d, end=%d, expanded=%d (diff=%d)\n", __func__, 3351 start, end, (len - start_len), (end - start)); 3352 3353 return (len - start_len); 3354 } 3355 3356 /* 3357 * Calculate the tab stops on a given line in the field and set up 3358 * the tabs list with the results. We do this by scanning the line for tab 3359 * characters and if one is found, noting the position and the number of 3360 * characters to get to the next tab stop. This information is kept to 3361 * make manipulating the field (scrolling and so on) easier to handle. 3362 */ 3363 void 3364 _formi_calculate_tabs(_FORMI_FIELD_LINES *row) 3365 { 3366 _formi_tab_t *ts = row->tabs, *old_ts = NULL, **tsp; 3367 int i, j; 3368 3369 /* 3370 * If the line already has tabs then invalidate them by 3371 * walking the list and killing the in_use flag. 3372 */ 3373 for (; ts != NULL; ts = ts->fwd) 3374 ts->in_use = FALSE; 3375 3376 3377 /* 3378 * Now look for tabs in the row and record the info... 3379 */ 3380 tsp = &row->tabs; 3381 for (i = 0, j = 0; i < row->length; i++, j++) { 3382 if (row->string[i] == '\t') { 3383 if (*tsp == NULL) { 3384 if ((*tsp = malloc(sizeof(*tsp))) == NULL) 3385 return; 3386 (*tsp)->back = old_ts; 3387 (*tsp)->fwd = NULL; 3388 } 3389 3390 (*tsp)->in_use = TRUE; 3391 (*tsp)->pos = i; 3392 (*tsp)->size = 8 - (j % 8); 3393 j += (*tsp)->size - 1; 3394 old_ts = *tsp; 3395 tsp = &(*tsp)->fwd; 3396 } 3397 } 3398 } 3399 3400 /* 3401 * Return the size of the tab padding for a tab character at the given 3402 * position. Return 1 if there is not a tab char entry matching the 3403 * given location. 3404 */ 3405 static int 3406 tab_size(_FORMI_FIELD_LINES *row, unsigned int i) 3407 { 3408 _formi_tab_t *ts; 3409 3410 ts = row->tabs; 3411 while ((ts != NULL) && (ts->pos != i)) 3412 ts = ts->fwd; 3413 3414 if (ts == NULL) 3415 return 1; 3416 else 3417 return ts->size; 3418 } 3419 3420 /* 3421 * Find the character offset that corresponds to longest tab expanded 3422 * string that will fit into the given window. Walk the string backwards 3423 * evaluating the sizes of any tabs that are in the string. Note that 3424 * using this function on a multi-line window will produce undefined 3425 * results - it is really only required for a single row field. 3426 */ 3427 static int 3428 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window) 3429 { 3430 int scroll_amt, i; 3431 _formi_tab_t *ts; 3432 3433 /* first find the last tab */ 3434 ts = field->alines->tabs; 3435 3436 /* 3437 * unless there are no tabs - just return the window size, 3438 * if there is enough room, otherwise 0. 3439 */ 3440 if (ts == NULL) { 3441 if (field->alines->length < window) 3442 return 0; 3443 else 3444 return field->alines->length - window + 1; 3445 } 3446 3447 while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE)) 3448 ts = ts->fwd; 3449 3450 /* 3451 * now walk backwards finding the first tab that is to the 3452 * left of our starting pos. 3453 */ 3454 while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos)) 3455 ts = ts->back; 3456 3457 scroll_amt = 0; 3458 for (i = pos; i >= 0; i--) { 3459 if (field->alines->string[i] == '\t') { 3460 assert((ts != NULL) && (ts->in_use == TRUE)); 3461 if (ts->pos == i) { 3462 if ((scroll_amt + ts->size) > window) { 3463 break; 3464 } 3465 scroll_amt += ts->size; 3466 ts = ts->back; 3467 } 3468 else 3469 assert(ts->pos == i); 3470 } else { 3471 scroll_amt++; 3472 if (scroll_amt > window) 3473 break; 3474 } 3475 } 3476 3477 return ++i; 3478 } 3479 3480 /* 3481 * Return the position of the last character that will fit into the 3482 * given width after tabs have been expanded for a given row of a given 3483 * field. 3484 */ 3485 static unsigned int 3486 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int width) 3487 { 3488 unsigned int pos, len, row_pos; 3489 _formi_tab_t *ts; 3490 3491 ts = row->tabs; 3492 pos = 0; 3493 len = 0; 3494 row_pos = 0; 3495 3496 if (width == 0) 3497 return 0; 3498 3499 while ((len < width) && (pos < row->length)) { 3500 if (row->string[pos] == '\t') { 3501 assert((ts != NULL) && (ts->in_use == TRUE)); 3502 if (ts->pos == row_pos) { 3503 if ((len + ts->size) > width) 3504 break; 3505 len += ts->size; 3506 ts = ts->fwd; 3507 } 3508 else 3509 assert(ts->pos == row_pos); 3510 } else 3511 len++; 3512 pos++; 3513 row_pos++; 3514 } 3515 3516 if (pos > 0) 3517 pos--; 3518 return pos; 3519 } 3520 3521 /* 3522 * Sync the field line structures with the contents of buffer 0 for that 3523 * field. We do this by walking all the line structures and concatenating 3524 * all the strings into one single string in buffer 0. 3525 */ 3526 int 3527 _formi_sync_buffer(FIELD *field) 3528 { 3529 _FORMI_FIELD_LINES *line; 3530 char *nstr, *tmp; 3531 unsigned length; 3532 3533 if (field->alines == NULL) 3534 return E_BAD_ARGUMENT; 3535 3536 if (field->alines->string == NULL) 3537 return E_BAD_ARGUMENT; 3538 3539 /* 3540 * init nstr up front, just in case there are no line contents, 3541 * this could happen if the field just contains hard returns. 3542 */ 3543 if ((nstr = malloc(sizeof(*nstr))) == NULL) 3544 return E_SYSTEM_ERROR; 3545 nstr[0] = '\0'; 3546 3547 line = field->alines; 3548 length = 1; /* allow for terminating null */ 3549 3550 while (line != NULL) { 3551 if (line->length != 0) { 3552 if ((tmp = realloc(nstr, 3553 (size_t) (length + line->length))) 3554 == NULL) { 3555 if (nstr != NULL) 3556 free(nstr); 3557 return (E_SYSTEM_ERROR); 3558 } 3559 3560 nstr = tmp; 3561 strcat(nstr, line->string); 3562 length += line->length; 3563 } 3564 3565 line = line->next; 3566 } 3567 3568 if (field->buffers[0].string != NULL) 3569 free(field->buffers[0].string); 3570 field->buffers[0].allocated = length; 3571 field->buffers[0].length = length - 1; 3572 field->buffers[0].string = nstr; 3573 return E_OK; 3574 } 3575 3576 3577 3578