1 /* $NetBSD: field.c,v 1.22 2003/03/09 00:57:17 lukem Exp $ */ 2 /*- 3 * Copyright (c) 1998-1999 Brett Lymn 4 * (blymn@baea.com.au, brett_lymn@yahoo.com.au) 5 * All rights reserved. 6 * 7 * This code has been donated to The NetBSD Foundation by the Author. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * 29 */ 30 31 #include <sys/cdefs.h> 32 __RCSID("$NetBSD: field.c,v 1.22 2003/03/09 00:57:17 lukem Exp $"); 33 34 #include <stdlib.h> 35 #include <strings.h> 36 #include <stdarg.h> 37 #include <form.h> 38 #include "internals.h" 39 40 extern FORM _formi_default_form; 41 42 FIELD _formi_default_field = { 43 0, /* rows in the field */ 44 0, /* columns in the field */ 45 0, /* dynamic rows */ 46 0, /* dynamic columns */ 47 0, /* maximum growth */ 48 0, /* starting row in the form subwindow */ 49 0, /* starting column in the form subwindow */ 50 0, /* number of off screen rows */ 51 0, /* index of this field in form fields array. */ 52 0, /* number of buffers associated with this field */ 53 FALSE, /* set to true if buffer 0 has changed. */ 54 NO_JUSTIFICATION, /* justification style of the field */ 55 FALSE, /* set to true if field is in overlay mode */ 56 0, /* starting char in string (horiz scroll) */ 57 0, /* starting line in field (vert scroll) */ 58 0, /* number of rows actually used in field */ 59 0, /* actual pos of cursor in row, not same as x pos due to tabs */ 60 0, /* x pos of cursor in field */ 61 0, /* y pos of cursor in field */ 62 0, /* start of a new page on the form if 1 */ 63 0, /* number of the page this field is on */ 64 A_NORMAL, /* character attributes for the foreground */ 65 A_NORMAL, /* character attributes for the background */ 66 ' ', /* padding character */ 67 DEFAULT_FORM_OPTS, /* options for the field */ 68 NULL, /* the form this field is bound to, if any */ 69 NULL, /* field above this one */ 70 NULL, /* field below this one */ 71 NULL, /* field to the left of this one */ 72 NULL, /* field to the right of this one */ 73 NULL, /* user defined pointer. */ 74 NULL, /* used if fields are linked */ 75 NULL, /* type struct for the field */ 76 {NULL, NULL}, /* circle queue glue for sorting fields */ 77 NULL, /* args for field type. */ 78 0, /* number of allocated slots in lines array */ 79 NULL, /* pointer to the array of lines structures. */ 80 NULL, /* array of buffers for the field */ 81 }; 82 83 /* internal function prototypes */ 84 static int 85 field_buffer_init(FIELD *field, int buffer, size_t len); 86 static FIELD * 87 _formi_create_field(FIELD *, int, int, int, int, int, int); 88 89 90 /* 91 * Set the userptr for the field 92 */ 93 int 94 set_field_userptr(FIELD *field, void *ptr) 95 { 96 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 97 98 fp->userptr = ptr; 99 100 return E_OK; 101 } 102 103 /* 104 * Return the userptr for the field. 105 */ 106 107 void * 108 field_userptr(FIELD *field) 109 { 110 if (field == NULL) 111 return _formi_default_field.userptr; 112 else 113 return field->userptr; 114 } 115 116 /* 117 * Set the options for the designated field. 118 */ 119 int 120 set_field_opts(FIELD *field, Form_Options options) 121 { 122 int i; 123 124 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 125 126 /* not allowed to set opts if the field is the current one */ 127 if ((field != NULL) && (field->parent != NULL) && 128 (field->parent->cur_field == field->index)) 129 return E_CURRENT; 130 131 if ((options & O_STATIC) == O_STATIC) { 132 for (i = 0; i < fp->nbuf; i++) { 133 if (fp->buffers[i].length > fp->cols) 134 fp->buffers[i].string[fp->cols] = '\0'; 135 } 136 } 137 138 fp->opts = options; 139 140 /* if appropriate, redraw the field */ 141 if ((field != NULL) && (field->parent != NULL) 142 && (field->parent->posted == 1)) { 143 _formi_redraw_field(field->parent, field->index); 144 pos_form_cursor(field->parent); 145 wrefresh(field->parent->scrwin); 146 } 147 148 return E_OK; 149 } 150 151 /* 152 * Turn on the passed field options. 153 */ 154 int 155 field_opts_on(FIELD *field, Form_Options options) 156 { 157 int i; 158 159 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 160 161 /* not allowed to set opts if the field is the current one */ 162 if ((field != NULL) && (field->parent != NULL) && 163 (field->parent->cur_field == field->index)) 164 return E_CURRENT; 165 166 if ((options & O_STATIC) == O_STATIC) { 167 for (i = 0; i < fp->nbuf; i++) { 168 if (fp->buffers[i].length > fp->cols) 169 fp->buffers[i].string[fp->cols] = '\0'; 170 } 171 } 172 173 fp->opts |= options; 174 175 /* if appropriate, redraw the field */ 176 if ((field != NULL) && (field->parent != NULL) 177 && (field->parent->posted == 1)) { 178 _formi_redraw_field(field->parent, field->index); 179 pos_form_cursor(field->parent); 180 wrefresh(field->parent->scrwin); 181 } 182 183 return E_OK; 184 } 185 186 /* 187 * Turn off the passed field options. 188 */ 189 int 190 field_opts_off(FIELD *field, Form_Options options) 191 { 192 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 193 194 /* not allowed to set opts if the field is the current one */ 195 if ((field != NULL) && (field->parent != NULL) && 196 (field->parent->cur_field == field->index)) 197 return E_CURRENT; 198 199 fp->opts &= ~options; 200 201 /* if appropriate, redraw the field */ 202 if ((field != NULL) && (field->parent != NULL) 203 && (field->parent->posted == 1)) { 204 _formi_redraw_field(field->parent, field->index); 205 pos_form_cursor(field->parent); 206 wrefresh(field->parent->scrwin); 207 } 208 209 return E_OK; 210 } 211 212 /* 213 * Return the field options associated with the passed field. 214 */ 215 Form_Options 216 field_opts(FIELD *field) 217 { 218 if (field == NULL) 219 return _formi_default_field.opts; 220 else 221 return field->opts; 222 } 223 224 /* 225 * Set the justification for the passed field. 226 */ 227 int 228 set_field_just(FIELD *field, int justification) 229 { 230 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 231 232 /* 233 * not allowed to set justification if the field is 234 * the current one 235 */ 236 if ((field != NULL) && (field->parent != NULL) && 237 (field->parent->cur_field == field->index)) 238 return E_CURRENT; 239 240 if ((justification < MIN_JUST_STYLE) /* check justification valid */ 241 || (justification > MAX_JUST_STYLE)) 242 return E_BAD_ARGUMENT; 243 244 /* only allow justification on static, single row fields */ 245 if (((fp->opts & O_STATIC) != O_STATIC) || 246 ((fp->rows + fp->nrows) > 1)) 247 return E_BAD_ARGUMENT; 248 249 fp->justification = justification; 250 251 _formi_init_field_xpos(fp); 252 253 return E_OK; 254 } 255 256 /* 257 * Return the justification style of the field passed. 258 */ 259 int 260 field_just(FIELD *field) 261 { 262 if (field == NULL) 263 return _formi_default_field.justification; 264 else 265 return field->justification; 266 } 267 268 /* 269 * Return information about the field passed. 270 */ 271 int 272 field_info(FIELD *field, int *rows, int *cols, int *frow, int *fcol, 273 int *nrow, int *nbuf) 274 { 275 if (field == NULL) 276 return E_BAD_ARGUMENT; 277 278 *rows = field->rows; 279 *cols = field->cols; 280 *frow = field->form_row; 281 *fcol = field->form_col; 282 *nrow = field->nrows; 283 *nbuf = field->nbuf; 284 285 return E_OK; 286 } 287 288 /* 289 * Report the dynamic field information. 290 */ 291 int 292 dynamic_field_info(FIELD *field, int *drows, int *dcols, int *max) 293 { 294 if (field == NULL) 295 return E_BAD_ARGUMENT; 296 297 if ((field->opts & O_STATIC) == O_STATIC) { 298 *drows = field->rows; 299 *dcols = field->cols; 300 } else { 301 *drows = field->drows; 302 *dcols = field->dcols; 303 } 304 305 *max = field->max; 306 307 return E_OK; 308 } 309 310 /* 311 * Init all the field variables, perform wrapping and other tasks 312 * after the field buffer is set. 313 */ 314 static int 315 field_buffer_init(FIELD *field, int buffer, size_t len) 316 { 317 int status; 318 319 if (buffer == 0) { 320 field->start_char = 0; 321 field->start_line = 0; 322 field->row_xpos = 0; 323 field->cursor_xpos = 0; 324 field->cursor_ypos = 0; 325 field->row_count = 1; /* must be at least one row */ 326 field->lines[0].start = 0; 327 field->lines[0].end = (len > 0)? (len - 1) : 0; 328 field->lines[0].length = 329 _formi_tab_expanded_length(field->buffers[0].string, 330 0, field->lines[0].end); 331 332 /* we have to hope the wrap works - if it does not then the 333 buffer is pretty much borked */ 334 status = _formi_wrap_field(field, 0); 335 if (status != E_OK) 336 return status; 337 338 /* 339 * calculate the tabs for a single row field, the 340 * multiline case is handled when the wrap is done. 341 */ 342 if (field->row_count == 1) 343 _formi_calculate_tabs(field, 0); 344 345 /* redraw the field to reflect the new contents. If the field 346 * is attached.... 347 */ 348 if ((field->parent != NULL) && (field->parent->posted == 1)) { 349 _formi_redraw_field(field->parent, field->index); 350 /* make sure cursor goes back to current field */ 351 pos_form_cursor(field->parent); 352 } 353 } 354 355 return E_OK; 356 } 357 358 359 /* 360 * Set the field buffer to the string that results from processing 361 * the given format (fmt) using sprintf. 362 */ 363 int 364 set_field_printf(FIELD *field, int buffer, char *fmt, ...) 365 { 366 int len; 367 va_list args; 368 369 if (field == NULL) 370 return E_BAD_ARGUMENT; 371 372 if (buffer >= field->nbuf) 373 return E_BAD_ARGUMENT; 374 375 va_start(args, fmt); 376 /* check for buffer already existing, free the storage */ 377 if (field->buffers[buffer].allocated != 0) 378 free(field->buffers[buffer].string); 379 380 len = vasprintf(&field->buffers[buffer].string, fmt, args); 381 va_end(args); 382 if (len < 0) 383 return E_SYSTEM_ERROR; 384 385 field->buffers[buffer].length = len; 386 field->buffers[buffer].allocated = len + 1; 387 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols) 388 && ((field->rows + field->nrows) == 1)) 389 len = field->cols; 390 391 field->buffers[buffer].string[len] = '\0'; 392 return field_buffer_init(field, buffer, (unsigned int) len); 393 } 394 395 /* 396 * Set the value of the field buffer to the value given. 397 */ 398 399 int 400 set_field_buffer(FIELD *field, int buffer, char *value) 401 { 402 size_t len; 403 int status; 404 405 if (field == NULL) 406 return E_BAD_ARGUMENT; 407 408 if (buffer >= field->nbuf) /* make sure buffer is valid */ 409 return E_BAD_ARGUMENT; 410 411 len = strlen(value); 412 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols) 413 && ((field->rows + field->nrows) == 1)) 414 len = field->cols; 415 416 #ifdef DEBUG 417 if (_formi_create_dbg_file() != E_OK) 418 return E_SYSTEM_ERROR; 419 420 fprintf(dbg, 421 "set_field_buffer: entry: len = %d, value = %s, buffer=%d\n", 422 len, value, buffer); 423 fprintf(dbg, "set_field_buffer: entry: string = "); 424 if (field->buffers[buffer].string != NULL) 425 fprintf(dbg, "%s, len = %d\n", field->buffers[buffer].string, 426 field->buffers[buffer].length); 427 else 428 fprintf(dbg, "(null), len = 0\n"); 429 fprintf(dbg, "set_field_buffer: entry: lines.len = %d\n", 430 field->lines[0].length); 431 #endif 432 433 if ((field->buffers[buffer].string = 434 (char *) realloc(field->buffers[buffer].string, len + 1)) == NULL) 435 return E_SYSTEM_ERROR; 436 437 strlcpy(field->buffers[buffer].string, value, len + 1); 438 field->buffers[buffer].length = len; 439 field->buffers[buffer].allocated = len + 1; 440 status = field_buffer_init(field, buffer, len); 441 442 #ifdef DEBUG 443 fprintf(dbg, "set_field_buffer: exit: len = %d, value = %s\n", 444 len, value); 445 fprintf(dbg, "set_field_buffer: exit: string = %s, len = %d\n", 446 field->buffers[buffer].string, field->buffers[buffer].length); 447 fprintf(dbg, "set_field_buffer: exit: lines.len = %d\n", 448 field->lines[0].length); 449 #endif 450 451 return status; 452 } 453 454 /* 455 * Return the requested field buffer to the caller. 456 */ 457 char * 458 field_buffer(FIELD *field, int buffer) 459 { 460 461 if (field == NULL) 462 return NULL; 463 464 if (buffer >= field->nbuf) 465 return NULL; 466 467 return field->buffers[buffer].string; 468 } 469 470 /* 471 * Set the buffer 0 field status. 472 */ 473 int 474 set_field_status(FIELD *field, int status) 475 { 476 477 if (field == NULL) 478 return E_BAD_ARGUMENT; 479 480 if (status != FALSE) 481 field->buf0_status = TRUE; 482 else 483 field->buf0_status = FALSE; 484 485 return E_OK; 486 } 487 488 /* 489 * Return the buffer 0 status flag for the given field. 490 */ 491 int 492 field_status(FIELD *field) 493 { 494 495 if (field == NULL) /* the default buffer 0 never changes :-) */ 496 return FALSE; 497 498 return field->buf0_status; 499 } 500 501 /* 502 * Set the maximum growth for a dynamic field. 503 */ 504 int 505 set_max_field(FIELD *fptr, int max) 506 { 507 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 508 509 if ((field->opts & O_STATIC) == O_STATIC) /* check if field dynamic */ 510 return E_BAD_ARGUMENT; 511 512 if (max < 0) /* negative numbers are bad.... */ 513 return E_BAD_ARGUMENT; 514 515 field->max = max; 516 return E_OK; 517 } 518 519 /* 520 * Set the field foreground character attributes. 521 */ 522 int 523 set_field_fore(FIELD *fptr, chtype attribute) 524 { 525 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 526 527 field->fore = attribute; 528 return E_OK; 529 } 530 531 /* 532 * Return the foreground character attribute for the given field. 533 */ 534 chtype 535 field_fore(FIELD *field) 536 { 537 if (field == NULL) 538 return _formi_default_field.fore; 539 else 540 return field->fore; 541 } 542 543 /* 544 * Set the background character attribute for the given field. 545 */ 546 int 547 set_field_back(FIELD *field, chtype attribute) 548 { 549 if (field == NULL) 550 _formi_default_field.back = attribute; 551 else 552 field->back = attribute; 553 554 return E_OK; 555 } 556 557 /* 558 * Get the background character attribute for the given field. 559 */ 560 chtype 561 field_back(FIELD *field) 562 { 563 if (field == NULL) 564 return _formi_default_field.back; 565 else 566 return field->back; 567 } 568 569 /* 570 * Set the pad character for the given field. 571 */ 572 int 573 set_field_pad(FIELD *field, int pad) 574 { 575 if (field == NULL) 576 _formi_default_field.pad = pad; 577 else 578 field->pad = pad; 579 580 return E_OK; 581 } 582 583 /* 584 * Return the padding character for the given field. 585 */ 586 int 587 field_pad(FIELD *field) 588 { 589 if (field == NULL) 590 return _formi_default_field.pad; 591 else 592 return field->pad; 593 } 594 595 /* 596 * Set the field initialisation function hook. 597 */ 598 int 599 set_field_init(FORM *form, Form_Hook function) 600 { 601 if (form == NULL) 602 _formi_default_form.field_init = function; 603 else 604 form->field_init = function; 605 606 return E_OK; 607 } 608 609 /* 610 * Return the function hook for the field initialisation. 611 */ 612 Form_Hook 613 field_init(FORM *form) 614 { 615 if (form == NULL) 616 return _formi_default_form.field_init; 617 else 618 return form->field_init; 619 } 620 621 /* 622 * Set the field termination function hook. 623 */ 624 int 625 set_field_term(FORM *form, Form_Hook function) 626 { 627 if (form == NULL) 628 _formi_default_form.field_term = function; 629 else 630 form->field_term = function; 631 632 return E_OK; 633 } 634 635 /* 636 * Return the function hook defined for the field termination. 637 */ 638 Form_Hook 639 field_term(FORM *form) 640 { 641 if (form == NULL) 642 return _formi_default_form.field_term; 643 else 644 return form->field_term; 645 } 646 647 /* 648 * Set the page flag on the given field to indicate it is the start of a 649 * new page. 650 */ 651 int 652 set_new_page(FIELD *fptr, int page) 653 { 654 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 655 656 if (field->parent != NULL) /* check if field is connected to a form */ 657 return E_CONNECTED; 658 659 field->page_break = (page != FALSE); 660 return E_OK; 661 } 662 663 /* 664 * Return the page status for the given field. TRUE is returned if the 665 * field is the start of a new page. 666 */ 667 int 668 new_page(FIELD *field) 669 { 670 if (field == NULL) 671 return _formi_default_field.page_break; 672 else 673 return field->page_break; 674 } 675 676 /* 677 * Return the index of the field in the form fields array. 678 */ 679 int 680 field_index(FIELD *field) 681 { 682 if (field == NULL) 683 return E_BAD_ARGUMENT; 684 685 if (field->parent == NULL) 686 return E_NOT_CONNECTED; 687 688 return field->index; 689 } 690 691 /* 692 * Internal function that does most of the work to create a new field. 693 * The new field is initialised from the information in the prototype 694 * field passed. 695 * Returns NULL on error. 696 */ 697 static FIELD * 698 _formi_create_field(FIELD *prototype, int rows, int cols, int frow, 699 int fcol, int nrows, int nbuf) 700 { 701 FIELD *new; 702 703 if ((rows <= 0) || (cols <= 0) || (frow < 0) || (fcol < 0) || 704 (nrows < 0) || (nbuf < 0)) 705 return NULL; 706 707 if ((new = (FIELD *)malloc(sizeof(FIELD))) == NULL) { 708 return NULL; 709 } 710 711 /* copy in the default field info */ 712 bcopy(prototype, new, sizeof(FIELD)); 713 714 new->nbuf = nbuf + 1; 715 new->rows = rows; 716 new->cols = cols; 717 new->form_row = frow; 718 new->form_col = fcol; 719 new->nrows = nrows; 720 new->link = new; 721 return new; 722 } 723 724 /* 725 * Create a new field structure. 726 */ 727 FIELD * 728 new_field(int rows, int cols, int frow, int fcol, int nrows, int nbuf) 729 { 730 FIELD *new; 731 size_t buf_len; 732 int i; 733 734 735 if ((new = _formi_create_field(&_formi_default_field, rows, cols, 736 frow, fcol, nrows, nbuf)) == NULL) 737 return NULL; 738 739 buf_len = (nbuf + 1) * sizeof(FORM_STR); 740 741 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) { 742 free(new); 743 return NULL; 744 } 745 746 /* Initialise the strings to a zero length string */ 747 for (i = 0; i < nbuf + 1; i++) { 748 if ((new->buffers[i].string = 749 (char *) malloc(sizeof(char))) == NULL) { 750 free(new->buffers); 751 free(new); 752 return NULL; 753 } 754 new->buffers[i].string[0] = '\0'; 755 new->buffers[i].length = 0; 756 new->buffers[i].allocated = 1; 757 } 758 759 if ((new->lines = (_FORMI_FIELD_LINES *) 760 malloc(sizeof(struct _formi_field_lines))) == NULL) { 761 free(new->buffers); 762 free(new); 763 return NULL; 764 } 765 766 new->lines_alloced = 1; 767 new->lines[0].length = 0; 768 new->lines[0].start = 0; 769 new->lines[0].end = 0; 770 new->lines[0].tabs = NULL; 771 772 return new; 773 } 774 775 /* 776 * Duplicate the given field, including it's buffers. 777 */ 778 FIELD * 779 dup_field(FIELD *field, int frow, int fcol) 780 { 781 FIELD *new; 782 size_t row_len, buf_len; 783 784 if (field == NULL) 785 return NULL; 786 787 /* XXXX this right???? */ 788 if ((new = _formi_create_field(field, (int) field->rows, 789 (int ) field->cols, 790 frow, fcol, (int) field->nrows, 791 field->nbuf - 1)) == NULL) 792 return NULL; 793 794 row_len = (field->rows + field->nrows + 1) * field->cols; 795 buf_len = (field->nbuf + 1) * row_len * sizeof(FORM_STR); 796 797 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) { 798 free(new); 799 return NULL; 800 } 801 802 /* copy the buffers from the source field into the new copy */ 803 bcopy(field->buffers, new->buffers, buf_len); 804 805 return new; 806 } 807 808 /* 809 * Create a new field at the specified location by duplicating the given 810 * field. The buffers are shared with the parent field. 811 */ 812 FIELD * 813 link_field(FIELD *field, int frow, int fcol) 814 { 815 FIELD *new; 816 817 if (field == NULL) 818 return NULL; 819 820 if ((new = _formi_create_field(field, (int) field->rows, 821 (int) field->cols, 822 frow, fcol, (int) field->nrows, 823 field->nbuf - 1)) == NULL) 824 return NULL; 825 826 new->link = field->link; 827 field->link = new; 828 829 /* we are done. The buffer pointer was copied during the field 830 creation. */ 831 return new; 832 } 833 834 /* 835 * Release all storage allocated to the field 836 */ 837 int 838 free_field(FIELD *field) 839 { 840 FIELD *flink; 841 int i; 842 _formi_tab_t *ts, *nts; 843 844 if (field == NULL) 845 return E_BAD_ARGUMENT; 846 847 if (field->parent != NULL) 848 return E_CONNECTED; 849 850 if (field->link == field) { /* check if field linked */ 851 /* no it is not - release the buffers */ 852 free(field->buffers); 853 /* free the tab structures */ 854 for (i = 0; i < field->row_count - 1; i++) { 855 if (field->lines[i].tabs != NULL) { 856 ts = field->lines[i].tabs; 857 while (ts != NULL) { 858 nts = ts->fwd; 859 free(ts); 860 ts = nts; 861 } 862 } 863 } 864 } else { 865 /* is linked, traverse the links to find the field referring 866 * to the one to be freed. 867 */ 868 for (flink = field->link; flink != field; flink = flink->link); 869 flink->link = field->link; 870 } 871 872 free(field); 873 return E_OK; 874 } 875 876 877