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