xref: /freebsd/contrib/dialog/inputstr.c (revision 48c5129f93c5eb5419c87b08e4677d51513f1dc0)
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