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