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