1 /* 2 * $Id: inputstr.c,v 1.91 2021/01/17 22:19:05 tom Exp $ 3 * 4 * inputstr.c -- functions for input/display of a string 5 * 6 * Copyright 2000-2019,2021 Thomas E. Dickey 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU Lesser General Public License, version 2.1 10 * as published by the Free Software Foundation. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program; if not, write to 19 * Free Software Foundation, Inc. 20 * 51 Franklin St., Fifth Floor 21 * Boston, MA 02110, USA. 22 */ 23 24 #include <dialog.h> 25 #include <dlg_keys.h> 26 27 #include <errno.h> 28 29 #ifdef HAVE_SETLOCALE 30 #include <locale.h> 31 #endif 32 33 #if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH) 34 #include <search.h> 35 #else 36 #undef HAVE_TSEARCH 37 #endif 38 39 #ifdef NEED_WCHAR_H 40 #include <wchar.h> 41 #endif 42 43 #if defined(USE_WIDE_CURSES) 44 #define USE_CACHING 1 45 #elif defined(HAVE_XDIALOG) 46 #define USE_CACHING 1 /* editbox really needs caching! */ 47 #else 48 #define USE_CACHING 0 49 #endif 50 51 typedef struct _cache { 52 struct _cache *next; 53 #if USE_CACHING 54 int cache_num; /* tells what type of data is in list[] */ 55 const char *string_at; /* unique: associate caches by char* */ 56 #endif 57 size_t s_len; /* strlen(string) - we add 1 for EOS */ 58 size_t i_len; /* length(list) - we add 1 for EOS */ 59 char *string; /* a copy of the last-processed string */ 60 int *list; /* indices into the string */ 61 } CACHE; 62 63 #if USE_CACHING 64 #define SAME_CACHE(c,s,l) (c->string != 0 && memcmp(c->string,s,l) == 0) 65 66 static CACHE *cache_list; 67 68 typedef enum { 69 cInxCols 70 ,cCntWideBytes 71 ,cCntWideChars 72 ,cInxWideChars 73 ,cMAX 74 } CACHE_USED; 75 76 #ifdef HAVE_TSEARCH 77 static void *sorted_cache; 78 #endif 79 80 #ifdef USE_WIDE_CURSES 81 static int 82 have_locale(void) 83 { 84 static int result = -1; 85 if (result < 0) { 86 char *test = setlocale(LC_ALL, 0); 87 if (test == 0 || *test == 0) { 88 result = FALSE; 89 } else if (strcmp(test, "C") && strcmp(test, "POSIX")) { 90 result = TRUE; 91 } else { 92 result = FALSE; 93 } 94 } 95 return result; 96 } 97 #endif 98 99 #ifdef HAVE_TSEARCH 100 101 #if 0 102 static void 103 show_tsearch(const void *nodep, const VISIT which, const int depth) 104 { 105 const CACHE *p = *(CACHE * const *) nodep; 106 (void) depth; 107 if (which == postorder || which == leaf) { 108 DLG_TRACE(("# cache %p %p:%s\n", p, p->string, p->string)); 109 } 110 } 111 112 static void 113 trace_cache(const char *fn, int ln) 114 { 115 DLG_TRACE(("# trace_cache %s@%d\n", fn, ln)); 116 twalk(sorted_cache, show_tsearch); 117 } 118 119 #else 120 #define trace_cache(fn, ln) /* nothing */ 121 #endif 122 123 #define CMP(a,b) (((a) > (b)) ? 1 : (((a) < (b)) ? -1 : 0)) 124 125 static int 126 compare_cache(const void *a, const void *b) 127 { 128 const CACHE *p = (const CACHE *) a; 129 const CACHE *q = (const CACHE *) b; 130 int result = CMP(p->cache_num, q->cache_num); 131 if (result == 0) 132 result = CMP(p->string_at, q->string_at); 133 return result; 134 } 135 #endif 136 137 static CACHE * 138 find_cache(int cache_num, const char *string) 139 { 140 CACHE *p; 141 142 #ifdef HAVE_TSEARCH 143 void *pp; 144 CACHE find; 145 146 memset(&find, 0, sizeof(find)); 147 find.cache_num = cache_num; 148 find.string_at = string; 149 150 if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) { 151 p = *(CACHE **) pp; 152 } else { 153 p = 0; 154 } 155 #else 156 for (p = cache_list; p != 0; p = p->next) { 157 if (p->string_at == string) { 158 break; 159 } 160 } 161 #endif 162 return p; 163 } 164 165 static CACHE * 166 make_cache(int cache_num, const char *string) 167 { 168 CACHE *p; 169 170 p = dlg_calloc(CACHE, 1); 171 assert_ptr(p, "load_cache"); 172 p->next = cache_list; 173 cache_list = p; 174 175 p->cache_num = cache_num; 176 p->string_at = string; 177 178 #ifdef HAVE_TSEARCH 179 (void) tsearch(p, &sorted_cache, compare_cache); 180 #endif 181 return p; 182 } 183 184 static CACHE * 185 load_cache(int cache_num, const char *string) 186 { 187 CACHE *p; 188 189 if ((p = find_cache(cache_num, string)) == 0) { 190 p = make_cache(cache_num, string); 191 } 192 return p; 193 } 194 #else 195 static CACHE my_cache; 196 #define SAME_CACHE(c,s,l) (c->string != 0) 197 #define load_cache(cache, string) &my_cache 198 #endif /* USE_CACHING */ 199 200 /* 201 * If the given string has not changed, we do not need to update the index. 202 * If we need to update the index, allocate enough memory for it. 203 */ 204 static bool 205 same_cache2(CACHE * cache, const char *string, unsigned i_len) 206 { 207 size_t s_len = strlen(string); 208 bool result = TRUE; 209 210 if (cache->s_len == 0 211 || cache->s_len < s_len 212 || cache->list == 0 213 || !SAME_CACHE(cache, string, (size_t) s_len)) { 214 unsigned need = (i_len + 1); 215 216 if (cache->list == 0) { 217 cache->list = dlg_malloc(int, need); 218 } else if (cache->i_len < i_len) { 219 cache->list = dlg_realloc(int, need, cache->list); 220 } 221 assert_ptr(cache->list, "load_cache"); 222 cache->i_len = i_len; 223 224 if (cache->s_len >= s_len && cache->string != 0) { 225 strcpy(cache->string, string); 226 } else { 227 if (cache->string != 0) 228 free(cache->string); 229 cache->string = dlg_strclone(string); 230 } 231 cache->s_len = s_len; 232 233 result = FALSE; 234 } 235 return result; 236 } 237 238 #ifdef USE_WIDE_CURSES 239 /* 240 * Like same_cache2(), but we are only concerned about caching a copy of the 241 * string and its associated length. 242 */ 243 static bool 244 same_cache1(CACHE * cache, const char *string, size_t i_len) 245 { 246 size_t s_len = strlen(string); 247 bool result = TRUE; 248 249 if (cache->s_len != s_len 250 || !SAME_CACHE(cache, string, (size_t) s_len)) { 251 252 if (cache->s_len >= s_len && cache->string != 0) { 253 strcpy(cache->string, string); 254 } else { 255 if (cache->string != 0) 256 free(cache->string); 257 cache->string = dlg_strclone(string); 258 } 259 cache->s_len = s_len; 260 cache->i_len = i_len; 261 262 result = FALSE; 263 } 264 return result; 265 } 266 #endif /* USE_CACHING */ 267 268 /* 269 * Counts the number of bytes that make up complete wide-characters, up to byte 270 * 'len'. If there is no locale set, simply return the original length. 271 */ 272 #ifdef USE_WIDE_CURSES 273 static int 274 dlg_count_wcbytes(const char *string, size_t len) 275 { 276 int result; 277 278 if (have_locale()) { 279 CACHE *cache = load_cache(cCntWideBytes, string); 280 if (!same_cache1(cache, string, len)) { 281 while (len != 0) { 282 size_t code = 0; 283 const char *src = cache->string; 284 mbstate_t state; 285 char save = cache->string[len]; 286 287 cache->string[len] = '\0'; 288 memset(&state, 0, sizeof(state)); 289 code = mbsrtowcs((wchar_t *) 0, &src, len, &state); 290 cache->string[len] = save; 291 if ((int) code >= 0) { 292 break; 293 } 294 --len; 295 } 296 cache->i_len = len; 297 } 298 result = (int) cache->i_len; 299 } else { 300 result = (int) len; 301 } 302 return result; 303 } 304 #endif /* USE_WIDE_CURSES */ 305 306 /* 307 * Counts the number of wide-characters in the string. 308 */ 309 int 310 dlg_count_wchars(const char *string) 311 { 312 int result; 313 #ifdef USE_WIDE_CURSES 314 315 if (have_locale()) { 316 size_t len = strlen(string); 317 CACHE *cache = load_cache(cCntWideChars, string); 318 319 if (!same_cache1(cache, string, len)) { 320 const char *src = cache->string; 321 mbstate_t state; 322 int part = dlg_count_wcbytes(cache->string, len); 323 char save = cache->string[part]; 324 wchar_t *temp = dlg_calloc(wchar_t, len + 1); 325 326 if (temp != 0) { 327 size_t code; 328 329 cache->string[part] = '\0'; 330 memset(&state, 0, sizeof(state)); 331 code = mbsrtowcs(temp, &src, (size_t) part, &state); 332 cache->i_len = ((int) code >= 0) ? wcslen(temp) : 0; 333 cache->string[part] = save; 334 free(temp); 335 } else { 336 cache->i_len = 0; 337 } 338 } 339 result = (int) cache->i_len; 340 } else 341 #endif /* USE_WIDE_CURSES */ 342 { 343 result = (int) strlen(string); 344 } 345 return result; 346 } 347 348 /* 349 * Build an index of the wide-characters in the string, so we can easily tell 350 * which byte-offset begins a given wide-character. 351 */ 352 const int * 353 dlg_index_wchars(const char *string) 354 { 355 unsigned len = (unsigned) dlg_count_wchars(string); 356 CACHE *cache = load_cache(cInxWideChars, string); 357 358 if (!same_cache2(cache, string, len)) { 359 const char *current = string; 360 unsigned inx; 361 362 cache->list[0] = 0; 363 for (inx = 1; inx <= len; ++inx) { 364 #ifdef USE_WIDE_CURSES 365 if (have_locale()) { 366 mbstate_t state; 367 int width; 368 memset(&state, 0, sizeof(state)); 369 width = (int) mbrlen(current, strlen(current), &state); 370 if (width <= 0) 371 width = 1; /* FIXME: what if we have a control-char? */ 372 current += width; 373 cache->list[inx] = cache->list[inx - 1] + width; 374 } else 375 #endif /* USE_WIDE_CURSES */ 376 { 377 (void) current; 378 cache->list[inx] = (int) inx; 379 } 380 } 381 } 382 return cache->list; 383 } 384 385 /* 386 * Given the character-offset to find in the list, return the corresponding 387 * array index. 388 */ 389 int 390 dlg_find_index(const int *list, int limit, int to_find) 391 { 392 int result; 393 for (result = 0; result <= limit; ++result) { 394 if (to_find == list[result] 395 || result == limit 396 || ((result < limit) && (to_find < list[result + 1]))) { 397 break; 398 } 399 } 400 return result; 401 } 402 403 /* 404 * Build a list of the display-columns for the given string's characters. 405 */ 406 const int * 407 dlg_index_columns(const char *string) 408 { 409 unsigned len = (unsigned) dlg_count_wchars(string); 410 CACHE *cache = load_cache(cInxCols, string); 411 412 if (!same_cache2(cache, string, len)) { 413 414 cache->list[0] = 0; 415 #ifdef USE_WIDE_CURSES 416 if (have_locale()) { 417 unsigned inx; 418 size_t num_bytes = strlen(string); 419 const int *inx_wchars = dlg_index_wchars(string); 420 mbstate_t state; 421 422 for (inx = 0; inx < len; ++inx) { 423 int result; 424 425 if (string[inx_wchars[inx]] == TAB) { 426 result = ((cache->list[inx] | 7) + 1) - cache->list[inx]; 427 } else { 428 wchar_t temp[2]; 429 size_t check; 430 431 memset(&state, 0, sizeof(state)); 432 memset(temp, 0, sizeof(temp)); 433 check = mbrtowc(temp, 434 string + inx_wchars[inx], 435 num_bytes - (size_t) inx_wchars[inx], 436 &state); 437 if ((int) check <= 0) { 438 result = 1; 439 } else { 440 result = wcwidth(temp[0]); 441 } 442 if (result < 0) { 443 const wchar_t *printable; 444 cchar_t temp2, *temp2p = &temp2; 445 setcchar(temp2p, temp, 0, 0, 0); 446 printable = wunctrl(temp2p); 447 result = printable ? (int) wcslen(printable) : 1; 448 } 449 } 450 cache->list[inx + 1] = result; 451 if (inx != 0) 452 cache->list[inx + 1] += cache->list[inx]; 453 } 454 } else 455 #endif /* USE_WIDE_CURSES */ 456 { 457 unsigned inx; 458 459 for (inx = 0; inx < len; ++inx) { 460 chtype ch = UCH(string[inx]); 461 462 if (ch == TAB) 463 cache->list[inx + 1] = 464 ((cache->list[inx] | 7) + 1) - cache->list[inx]; 465 else if (isprint(UCH(ch))) 466 cache->list[inx + 1] = 1; 467 else { 468 const char *printable; 469 printable = unctrl(ch); 470 cache->list[inx + 1] = (printable 471 ? (int) strlen(printable) 472 : 1); 473 } 474 if (inx != 0) 475 cache->list[inx + 1] += cache->list[inx]; 476 } 477 } 478 } 479 return cache->list; 480 } 481 482 /* 483 * Returns the number of columns used for a string. That happens to be the 484 * end-value of the cols[] array. 485 */ 486 int 487 dlg_count_columns(const char *string) 488 { 489 int result = 0; 490 int limit = dlg_count_wchars(string); 491 if (limit > 0) { 492 const int *cols = dlg_index_columns(string); 493 result = cols[limit]; 494 } else { 495 result = (int) strlen(string); 496 } 497 dlg_finish_string(string); 498 return result; 499 } 500 501 /* 502 * Given a column limit, count the number of wide characters that can fit 503 * into that limit. The offset is used to skip over a leading character 504 * that was already written. 505 */ 506 int 507 dlg_limit_columns(const char *string, int limit, int offset) 508 { 509 const int *cols = dlg_index_columns(string); 510 int result = dlg_count_wchars(string); 511 512 while (result > 0 && (cols[result] - cols[offset]) > limit) 513 --result; 514 return result; 515 } 516 517 /* 518 * Updates the string and character-offset, given various editing characters 519 * or literal characters which are inserted at the character-offset. 520 */ 521 bool 522 dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force) 523 { 524 int i; 525 int len = (int) strlen(string); 526 int limit = dlg_count_wchars(string); 527 const int *indx = dlg_index_wchars(string); 528 int offset = dlg_find_index(indx, limit, *chr_offset); 529 int max_len = dlg_max_input(MAX_LEN); 530 bool edit = TRUE; 531 532 /* transform editing characters into equivalent function-keys */ 533 if (!fkey) { 534 fkey = TRUE; /* assume we transform */ 535 switch (key) { 536 case 0: 537 break; 538 case ESC: 539 case TAB: 540 fkey = FALSE; /* this is used for navigation */ 541 break; 542 default: 543 fkey = FALSE; /* ...no, we did not transform */ 544 break; 545 } 546 } 547 548 if (fkey) { 549 switch (key) { 550 case 0: /* special case for loop entry */ 551 edit = force; 552 break; 553 case DLGK_GRID_LEFT: 554 if (*chr_offset && offset > 0) 555 *chr_offset = indx[offset - 1]; 556 break; 557 case DLGK_GRID_RIGHT: 558 if (offset < limit) 559 *chr_offset = indx[offset + 1]; 560 break; 561 case DLGK_BEGIN: 562 if (*chr_offset) 563 *chr_offset = 0; 564 break; 565 case DLGK_FINAL: 566 if (offset < limit) 567 *chr_offset = indx[limit]; 568 break; 569 case DLGK_DELETE_LEFT: 570 if (offset) { 571 int gap = indx[offset] - indx[offset - 1]; 572 *chr_offset = indx[offset - 1]; 573 if (gap > 0) { 574 for (i = *chr_offset; 575 (string[i] = string[i + gap]) != '\0'; 576 i++) { 577 ; 578 } 579 } 580 } 581 break; 582 case DLGK_DELETE_RIGHT: 583 if (limit) { 584 if (--limit == 0) { 585 string[*chr_offset = 0] = '\0'; 586 } else { 587 int gap = ((offset <= limit) 588 ? (indx[offset + 1] - indx[offset]) 589 : 0); 590 if (gap > 0) { 591 for (i = indx[offset]; 592 (string[i] = string[i + gap]) != '\0'; 593 i++) { 594 ; 595 } 596 } else if (offset > 0) { 597 string[indx[offset - 1]] = '\0'; 598 } 599 if (*chr_offset > indx[limit]) 600 *chr_offset = indx[limit]; 601 } 602 } 603 break; 604 case DLGK_DELETE_ALL: 605 string[*chr_offset = 0] = '\0'; 606 break; 607 case DLGK_ENTER: 608 edit = 0; 609 break; 610 #ifdef KEY_RESIZE 611 case KEY_RESIZE: 612 edit = 0; 613 break; 614 #endif 615 case DLGK_GRID_UP: 616 case DLGK_GRID_DOWN: 617 case DLGK_FIELD_NEXT: 618 case DLGK_FIELD_PREV: 619 edit = 0; 620 break; 621 case ERR: 622 edit = 0; 623 break; 624 default: 625 beep(); 626 break; 627 } 628 } else { 629 if (key == ESC || key == ERR) { 630 edit = 0; 631 } else { 632 if (len < max_len) { 633 for (i = ++len; i > *chr_offset; i--) 634 string[i] = string[i - 1]; 635 string[*chr_offset] = (char) key; 636 *chr_offset += 1; 637 } else { 638 (void) beep(); 639 } 640 } 641 } 642 return edit; 643 } 644 645 static void 646 compute_edit_offset(const char *string, 647 int chr_offset, 648 int x_last, 649 int *p_dpy_column, 650 int *p_scroll_amt) 651 { 652 const int *cols = dlg_index_columns(string); 653 const int *indx = dlg_index_wchars(string); 654 int limit = dlg_count_wchars(string); 655 int offset = dlg_find_index(indx, limit, chr_offset); 656 int offset2; 657 int dpy_column; 658 int n; 659 660 for (n = offset2 = 0; n <= offset; ++n) { 661 if ((cols[offset] - cols[n]) < x_last 662 && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) { 663 offset2 = n; 664 break; 665 } 666 } 667 668 dpy_column = cols[offset] - cols[offset2]; 669 670 if (p_dpy_column != 0) 671 *p_dpy_column = dpy_column; 672 if (p_scroll_amt != 0) 673 *p_scroll_amt = offset2; 674 } 675 676 /* 677 * Given the character-offset in the string, returns the display-offset where 678 * we will position the cursor. 679 */ 680 int 681 dlg_edit_offset(char *string, int chr_offset, int x_last) 682 { 683 int result; 684 685 compute_edit_offset(string, chr_offset, x_last, &result, 0); 686 687 return result; 688 } 689 690 /* 691 * Displays the string, shifted as necessary, to fit within the box and show 692 * the current character-offset. 693 */ 694 void 695 dlg_show_string(WINDOW *win, 696 const char *string, /* string to display (may be multibyte) */ 697 int chr_offset, /* character (not bytes) offset */ 698 chtype attr, /* window-attributes */ 699 int y_base, /* beginning row on screen */ 700 int x_base, /* beginning column on screen */ 701 int x_last, /* number of columns on screen */ 702 bool hidden, /* if true, do not echo */ 703 bool force) /* if true, force repaint */ 704 { 705 x_last = MIN(x_last + x_base, getmaxx(win)) - x_base; 706 707 if (hidden && !dialog_vars.insecure) { 708 if (force) { 709 (void) wmove(win, y_base, x_base); 710 wrefresh(win); 711 } 712 } else { 713 const int *cols = dlg_index_columns(string); 714 const int *indx = dlg_index_wchars(string); 715 int limit = dlg_count_wchars(string); 716 717 int i, j, k; 718 int input_x; 719 int scrollamt; 720 721 compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt); 722 723 dlg_attrset(win, attr); 724 (void) wmove(win, y_base, x_base); 725 for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) { 726 int check = cols[i + 1] - cols[scrollamt]; 727 if (check <= x_last) { 728 for (j = indx[i]; j < indx[i + 1]; ++j) { 729 chtype ch = UCH(string[j]); 730 if (hidden && dialog_vars.insecure) { 731 waddch(win, '*'); 732 } else if (ch == TAB) { 733 int count = cols[i + 1] - cols[i]; 734 while (--count >= 0) 735 waddch(win, ' '); 736 } else { 737 waddch(win, ch); 738 } 739 } 740 k = check; 741 } else { 742 break; 743 } 744 } 745 while (k++ < x_last) 746 waddch(win, ' '); 747 (void) wmove(win, y_base, x_base + input_x); 748 wrefresh(win); 749 } 750 } 751 752 /* 753 * Discard cached data for the given string. 754 */ 755 void 756 dlg_finish_string(const char *string) 757 { 758 #if USE_CACHING 759 if ((string != 0) && dialog_state.finish_string) { 760 CACHE *p = cache_list; 761 CACHE *q = 0; 762 CACHE *r; 763 764 while (p != 0) { 765 if (p->string_at == string) { 766 #ifdef HAVE_TSEARCH 767 if (tdelete(p, &sorted_cache, compare_cache) == 0) { 768 continue; 769 } 770 trace_cache(__FILE__, __LINE__); 771 #endif 772 if (p->string != 0) 773 free(p->string); 774 if (p->list != 0) 775 free(p->list); 776 if (p == cache_list) { 777 cache_list = p->next; 778 r = cache_list; 779 } else { 780 q->next = p->next; 781 r = q; 782 } 783 free(p); 784 p = r; 785 } else { 786 q = p; 787 p = p->next; 788 } 789 } 790 } 791 #else 792 (void) string; 793 #endif 794 } 795 796 #ifdef NO_LEAKS 797 void 798 _dlg_inputstr_leaks(void) 799 { 800 #if USE_CACHING 801 dialog_state.finish_string = TRUE; 802 trace_cache(__FILE__, __LINE__); 803 while (cache_list != 0) { 804 dlg_finish_string(cache_list->string_at); 805 } 806 #endif /* USE_CACHING */ 807 } 808 #endif /* NO_LEAKS */ 809