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
have_locale(void)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
compare_cache(const void * a,const void * b)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 *
find_cache(CACHE * cache,const char * string)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
make_cache(CACHE * cache,const char * string)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
load_cache(CACHE * cache,const char * string)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
save_cache(CACHE * cache,const char * string)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
same_cache2(CACHE * cache,const char * string,unsigned i_len)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
same_cache1(CACHE * cache,const char * string,size_t i_len)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
dlg_count_wcbytes(const char * string,size_t len)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
dlg_count_wchars(const char * string)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 *
dlg_index_wchars(const char * string)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
dlg_find_index(const int * list,int limit,int to_find)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 *
dlg_index_columns(const char * string)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
dlg_count_columns(const char * string)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
dlg_limit_columns(const char * string,int limit,int offset)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
dlg_edit_string(char * string,int * chr_offset,int key,int fkey,bool force)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
compute_edit_offset(const char * string,int chr_offset,int x_last,int * p_dpy_column,int * p_scroll_amt)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
dlg_edit_offset(char * string,int chr_offset,int x_last)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
dlg_show_string(WINDOW * win,const char * string,int chr_offset,chtype attr,int y_base,int x_base,int x_last,bool hidden,bool force)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
_dlg_inputstr_leaks(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