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