1 /* 2 * $Id: inputstr.c,v 1.69 2011/01/16 21:52:35 tom Exp $ 3 * 4 * inputstr.c -- functions for input/display of a string 5 * 6 * Copyright 2000-2010,2011 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 int part = 0; 265 size_t code = 0; 266 const char *src = cache.string; 267 mbstate_t state; 268 char save = cache.string[len]; 269 270 cache.string[len] = '\0'; 271 memset(&state, 0, sizeof(state)); 272 code = mbsrtowcs((wchar_t *) 0, &src, len, &state); 273 cache.string[len] = save; 274 if ((int) code >= 0) { 275 break; 276 } 277 ++part; 278 --len; 279 } 280 cache.i_len = len; 281 save_cache(&cache, string); 282 } 283 result = (int) cache.i_len; 284 } else { 285 result = (int) len; 286 } 287 return result; 288 } 289 #endif /* USE_WIDE_CURSES */ 290 291 /* 292 * Counts the number of wide-characters in the string. 293 */ 294 int 295 dlg_count_wchars(const char *string) 296 { 297 int result; 298 299 #ifdef USE_WIDE_CURSES 300 if (have_locale()) { 301 static CACHE cache; 302 size_t len = strlen(string); 303 304 load_cache(&cache, string); 305 if (!same_cache1(&cache, string, len)) { 306 const char *src = cache.string; 307 mbstate_t state; 308 int part = dlg_count_wcbytes(cache.string, len); 309 char save = cache.string[part]; 310 size_t code; 311 wchar_t *temp = dlg_calloc(wchar_t, len + 1); 312 313 cache.string[part] = '\0'; 314 memset(&state, 0, sizeof(state)); 315 code = mbsrtowcs(temp, &src, (size_t) part, &state); 316 cache.i_len = ((int) code >= 0) ? wcslen(temp) : 0; 317 cache.string[part] = save; 318 free(temp); 319 save_cache(&cache, string); 320 } 321 result = (int) cache.i_len; 322 } else 323 #endif /* USE_WIDE_CURSES */ 324 { 325 result = (int) strlen(string); 326 } 327 return result; 328 } 329 330 /* 331 * Build an index of the wide-characters in the string, so we can easily tell 332 * which byte-offset begins a given wide-character. 333 */ 334 const int * 335 dlg_index_wchars(const char *string) 336 { 337 static CACHE cache; 338 unsigned len = (unsigned) dlg_count_wchars(string); 339 unsigned inx; 340 341 load_cache(&cache, string); 342 if (!same_cache2(&cache, string, len)) { 343 const char *current = string; 344 345 cache.list[0] = 0; 346 for (inx = 1; inx <= len; ++inx) { 347 #ifdef USE_WIDE_CURSES 348 if (have_locale()) { 349 mbstate_t state; 350 int width; 351 memset(&state, 0, sizeof(state)); 352 width = (int) mbrlen(current, strlen(current), &state); 353 if (width <= 0) 354 width = 1; /* FIXME: what if we have a control-char? */ 355 current += width; 356 cache.list[inx] = cache.list[inx - 1] + width; 357 } else 358 #endif /* USE_WIDE_CURSES */ 359 { 360 (void) current; 361 cache.list[inx] = (int) inx; 362 } 363 } 364 save_cache(&cache, string); 365 } 366 return cache.list; 367 } 368 369 /* 370 * Given the character-offset to find in the list, return the corresponding 371 * array index. 372 */ 373 int 374 dlg_find_index(const int *list, int limit, int to_find) 375 { 376 int result; 377 for (result = 0; result <= limit; ++result) { 378 if (to_find == list[result] 379 || result == limit 380 || to_find < list[result + 1]) 381 break; 382 } 383 return result; 384 } 385 386 /* 387 * Build a list of the display-columns for the given string's characters. 388 */ 389 const int * 390 dlg_index_columns(const char *string) 391 { 392 static CACHE cache; 393 unsigned len = (unsigned) dlg_count_wchars(string); 394 unsigned inx; 395 396 load_cache(&cache, string); 397 if (!same_cache2(&cache, string, len)) { 398 cache.list[0] = 0; 399 #ifdef USE_WIDE_CURSES 400 if (have_locale()) { 401 size_t num_bytes = strlen(string); 402 const int *inx_wchars = dlg_index_wchars(string); 403 mbstate_t state; 404 405 for (inx = 0; inx < len; ++inx) { 406 wchar_t temp[2]; 407 size_t check; 408 int result; 409 410 if (string[inx_wchars[inx]] == TAB) { 411 result = ((cache.list[inx] | 7) + 1) - cache.list[inx]; 412 } else { 413 memset(&state, 0, sizeof(state)); 414 memset(temp, 0, sizeof(temp)); 415 check = mbrtowc(temp, 416 string + inx_wchars[inx], 417 num_bytes - (size_t) inx_wchars[inx], 418 &state); 419 if ((int) check <= 0) { 420 result = 1; 421 } else { 422 result = wcwidth(temp[0]); 423 } 424 if (result < 0) { 425 const wchar_t *printable; 426 cchar_t temp2, *temp2p = &temp2; 427 setcchar(temp2p, temp, 0, 0, 0); 428 printable = wunctrl(temp2p); 429 result = printable ? (int) wcslen(printable) : 1; 430 } 431 } 432 cache.list[inx + 1] = result; 433 if (inx != 0) 434 cache.list[inx + 1] += cache.list[inx]; 435 } 436 } else 437 #endif /* USE_WIDE_CURSES */ 438 { 439 for (inx = 0; inx < len; ++inx) { 440 chtype ch = UCH(string[inx]); 441 442 if (ch == TAB) 443 cache.list[inx + 1] = 444 ((cache.list[inx] | 7) + 1) - cache.list[inx]; 445 else if (isprint(ch)) 446 cache.list[inx + 1] = 1; 447 else { 448 const char *printable; 449 printable = unctrl(ch); 450 cache.list[inx + 1] = (printable 451 ? (int) strlen(printable) 452 : 1); 453 } 454 if (inx != 0) 455 cache.list[inx + 1] += cache.list[inx]; 456 } 457 } 458 save_cache(&cache, string); 459 } 460 return cache.list; 461 } 462 463 /* 464 * Returns the number of columns used for a string. That happens to be the 465 * end-value of the cols[] array. 466 */ 467 int 468 dlg_count_columns(const char *string) 469 { 470 int result = 0; 471 int limit = dlg_count_wchars(string); 472 if (limit > 0) { 473 const int *cols = dlg_index_columns(string); 474 result = cols[limit]; 475 } else { 476 result = (int) strlen(string); 477 } 478 return result; 479 } 480 481 /* 482 * Given a column limit, count the number of wide characters that can fit 483 * into that limit. The offset is used to skip over a leading character 484 * that was already written. 485 */ 486 int 487 dlg_limit_columns(const char *string, int limit, int offset) 488 { 489 const int *cols = dlg_index_columns(string); 490 int result = dlg_count_wchars(string); 491 492 while (result > 0 && (cols[result] - cols[offset]) > limit) 493 --result; 494 return result; 495 } 496 497 /* 498 * Updates the string and character-offset, given various editing characters 499 * or literal characters which are inserted at the character-offset. 500 */ 501 bool 502 dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force) 503 { 504 int i; 505 int len = (int) strlen(string); 506 int limit = dlg_count_wchars(string); 507 const int *indx = dlg_index_wchars(string); 508 int offset = dlg_find_index(indx, limit, *chr_offset); 509 int max_len = dlg_max_input(MAX_LEN); 510 bool edit = TRUE; 511 512 /* transform editing characters into equivalent function-keys */ 513 if (!fkey) { 514 fkey = TRUE; /* assume we transform */ 515 switch (key) { 516 case 0: 517 break; 518 case ESC: 519 case TAB: 520 fkey = FALSE; /* this is used for navigation */ 521 break; 522 default: 523 fkey = FALSE; /* ...no, we did not transform */ 524 break; 525 } 526 } 527 528 if (fkey) { 529 switch (key) { 530 case 0: /* special case for loop entry */ 531 edit = force; 532 break; 533 case DLGK_GRID_LEFT: 534 if (*chr_offset) 535 *chr_offset = indx[offset - 1]; 536 break; 537 case DLGK_GRID_RIGHT: 538 if (offset < limit) 539 *chr_offset = indx[offset + 1]; 540 break; 541 case DLGK_BEGIN: 542 if (*chr_offset) 543 *chr_offset = 0; 544 break; 545 case DLGK_FINAL: 546 if (offset < limit) 547 *chr_offset = indx[limit]; 548 break; 549 case DLGK_DELETE_LEFT: 550 if (offset) { 551 int gap = indx[offset] - indx[offset - 1]; 552 *chr_offset = indx[offset - 1]; 553 if (gap > 0) { 554 for (i = *chr_offset; 555 (string[i] = string[i + gap]) != '\0'; 556 i++) { 557 ; 558 } 559 } 560 } 561 break; 562 case DLGK_DELETE_RIGHT: 563 if (limit) { 564 if (--limit == 0) { 565 string[*chr_offset = 0] = '\0'; 566 } else { 567 int gap = ((offset <= limit) 568 ? (indx[offset + 1] - indx[offset]) 569 : 0); 570 if (gap > 0) { 571 for (i = indx[offset]; 572 (string[i] = string[i + gap]) != '\0'; 573 i++) { 574 ; 575 } 576 } else if (offset > 0) { 577 string[indx[offset - 1]] = '\0'; 578 } 579 if (*chr_offset > indx[limit]) 580 *chr_offset = indx[limit]; 581 } 582 } 583 break; 584 case DLGK_DELETE_ALL: 585 string[*chr_offset = 0] = '\0'; 586 break; 587 case DLGK_ENTER: 588 edit = 0; 589 break; 590 #ifdef KEY_RESIZE 591 case KEY_RESIZE: 592 edit = 0; 593 break; 594 #endif 595 case DLGK_GRID_UP: 596 case DLGK_GRID_DOWN: 597 case DLGK_FIELD_NEXT: 598 case DLGK_FIELD_PREV: 599 edit = 0; 600 break; 601 case ERR: 602 edit = 0; 603 break; 604 default: 605 beep(); 606 break; 607 } 608 } else { 609 if (key == ESC || key == ERR) { 610 edit = 0; 611 } else { 612 if (len < max_len) { 613 for (i = ++len; i > *chr_offset; i--) 614 string[i] = string[i - 1]; 615 string[*chr_offset] = (char) key; 616 *chr_offset += 1; 617 } else { 618 (void) beep(); 619 } 620 } 621 } 622 return edit; 623 } 624 625 static void 626 compute_edit_offset(const char *string, 627 int chr_offset, 628 int x_last, 629 int *p_dpy_column, 630 int *p_scroll_amt) 631 { 632 const int *cols = dlg_index_columns(string); 633 const int *indx = dlg_index_wchars(string); 634 int limit = dlg_count_wchars(string); 635 int offset = dlg_find_index(indx, limit, chr_offset); 636 int offset2; 637 int dpy_column; 638 int n; 639 640 for (n = offset2 = 0; n <= offset; ++n) { 641 if ((cols[offset] - cols[n]) < x_last 642 && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) { 643 offset2 = n; 644 break; 645 } 646 } 647 648 dpy_column = cols[offset] - cols[offset2]; 649 650 if (p_dpy_column != 0) 651 *p_dpy_column = dpy_column; 652 if (p_scroll_amt != 0) 653 *p_scroll_amt = offset2; 654 } 655 656 /* 657 * Given the character-offset in the string, returns the display-offset where 658 * we will position the cursor. 659 */ 660 int 661 dlg_edit_offset(char *string, int chr_offset, int x_last) 662 { 663 int result; 664 665 compute_edit_offset(string, chr_offset, x_last, &result, 0); 666 667 return result; 668 } 669 670 /* 671 * Displays the string, shifted as necessary, to fit within the box and show 672 * the current character-offset. 673 */ 674 void 675 dlg_show_string(WINDOW *win, 676 const char *string, /* string to display (may be multibyte) */ 677 int chr_offset, /* character (not bytes) offset */ 678 chtype attr, /* window-attributes */ 679 int y_base, /* beginning row on screen */ 680 int x_base, /* beginning column on screen */ 681 int x_last, /* number of columns on screen */ 682 bool hidden, /* if true, do not echo */ 683 bool force) /* if true, force repaint */ 684 { 685 x_last = MIN(x_last + x_base, getmaxx(win)) - x_base; 686 687 if (hidden && !dialog_vars.insecure) { 688 if (force) { 689 (void) wmove(win, y_base, x_base); 690 wrefresh(win); 691 } 692 } else { 693 const int *cols = dlg_index_columns(string); 694 const int *indx = dlg_index_wchars(string); 695 int limit = dlg_count_wchars(string); 696 697 int i, j, k; 698 int input_x; 699 int scrollamt; 700 701 compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt); 702 703 wattrset(win, attr); 704 (void) wmove(win, y_base, x_base); 705 for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) { 706 int check = cols[i + 1] - cols[scrollamt]; 707 if (check <= x_last) { 708 for (j = indx[i]; j < indx[i + 1]; ++j) { 709 chtype ch = UCH(string[j]); 710 if (hidden && dialog_vars.insecure) { 711 waddch(win, '*'); 712 } else if (ch == TAB) { 713 int count = cols[i + 1] - cols[i]; 714 while (--count >= 0) 715 waddch(win, ' '); 716 } else { 717 waddch(win, ch); 718 } 719 } 720 k = check; 721 } else { 722 break; 723 } 724 } 725 while (k++ < x_last) 726 waddch(win, ' '); 727 (void) wmove(win, y_base, x_base + input_x); 728 wrefresh(win); 729 } 730 } 731 732 #ifdef NO_LEAKS 733 void 734 _dlg_inputstr_leaks(void) 735 { 736 #if USE_CACHING 737 while (cache_list != 0) { 738 CACHE *next = cache_list->next; 739 #ifdef HAVE_TSEARCH 740 tdelete(cache_list, &sorted_cache, compare_cache); 741 #endif 742 if (cache_list->string != 0) 743 free(cache_list->string); 744 if (cache_list->list != 0) 745 free(cache_list->list); 746 free(cache_list); 747 cache_list = next; 748 } 749 #endif /* USE_CACHING */ 750 } 751 #endif /* NO_LEAKS */ 752