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
51typedef 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
66static CACHE *cache_list;
67
68typedef enum {
69    cInxCols
70    ,cCntWideBytes
71    ,cCntWideChars
72    ,cInxWideChars
73    ,cMAX
74} CACHE_USED;
75
76#ifdef HAVE_TSEARCH
77static void *sorted_cache;
78#endif
79
80#ifdef USE_WIDE_CURSES
81static int
82have_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
102static void
103show_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
112static void
113trace_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
123static int
124compare_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
135static CACHE *
136find_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
163static CACHE *
164make_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
182static CACHE *
183load_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
193static 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 */
202static bool
203same_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 */
242static bool
243same_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
272static int
273dlg_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 */
308int
309dlg_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 */
350const int *
351dlg_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 */
387int
388dlg_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 */
404const int *
405dlg_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 */
480int
481dlg_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 */
500int
501dlg_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 */
515bool
516dlg_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
639static void
640compute_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 */
674int
675dlg_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 */
688void
689dlg_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 */
749void
750dlg_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
791void
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