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