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