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