1217309Snwhitehorn/*
2255852Sdteske *  $Id: inputstr.c,v 1.83 2013/09/23 23:19:26 tom Exp $
3217309Snwhitehorn *
4220749Snwhitehorn *  inputstr.c -- functions for input/display of a string
5217309Snwhitehorn *
6255852Sdteske *  Copyright 2000-2012,2013	Thomas E. Dickey
7217309Snwhitehorn *
8217309Snwhitehorn *  This program is free software; you can redistribute it and/or modify
9217309Snwhitehorn *  it under the terms of the GNU Lesser General Public License, version 2.1
10217309Snwhitehorn *  as published by the Free Software Foundation.
11217309Snwhitehorn *
12217309Snwhitehorn *  This program is distributed in the hope that it will be useful, but
13217309Snwhitehorn *  WITHOUT ANY WARRANTY; without even the implied warranty of
14217309Snwhitehorn *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15217309Snwhitehorn *  Lesser General Public License for more details.
16217309Snwhitehorn *
17217309Snwhitehorn *  You should have received a copy of the GNU Lesser General Public
18217309Snwhitehorn *  License along with this program; if not, write to
19217309Snwhitehorn *	Free Software Foundation, Inc.
20217309Snwhitehorn *	51 Franklin St., Fifth Floor
21217309Snwhitehorn *	Boston, MA 02110, USA.
22217309Snwhitehorn */
23217309Snwhitehorn
24217309Snwhitehorn#include <dialog.h>
25217309Snwhitehorn#include <dlg_keys.h>
26217309Snwhitehorn
27217309Snwhitehorn#include <errno.h>
28217309Snwhitehorn
29217309Snwhitehorn#ifdef HAVE_SETLOCALE
30217309Snwhitehorn#include <locale.h>
31217309Snwhitehorn#endif
32217309Snwhitehorn
33217309Snwhitehorn#if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
34217309Snwhitehorn#include <search.h>
35217309Snwhitehorn#else
36217309Snwhitehorn#undef HAVE_TSEARCH
37217309Snwhitehorn#endif
38217309Snwhitehorn
39217309Snwhitehorn#ifdef NEED_WCHAR_H
40217309Snwhitehorn#include <wchar.h>
41217309Snwhitehorn#endif
42217309Snwhitehorn
43217309Snwhitehorn#if defined(USE_WIDE_CURSES)
44217309Snwhitehorn#define USE_CACHING 1
45217309Snwhitehorn#elif defined(HAVE_XDIALOG)
46217309Snwhitehorn#define USE_CACHING 1		/* editbox really needs caching! */
47217309Snwhitehorn#else
48217309Snwhitehorn#define USE_CACHING 0
49217309Snwhitehorn#endif
50217309Snwhitehorn
51217309Snwhitehorntypedef struct _cache {
52217309Snwhitehorn    struct _cache *next;
53217309Snwhitehorn#if USE_CACHING
54255852Sdteske    int cache_num;		/* tells what type of data is in list[] */
55217309Snwhitehorn    const char *string_at;	/* unique: associate caches by char* */
56217309Snwhitehorn#endif
57220749Snwhitehorn    size_t s_len;		/* strlen(string) - we add 1 for EOS */
58220749Snwhitehorn    size_t i_len;		/* length(list) - we add 1 for EOS */
59217309Snwhitehorn    char *string;		/* a copy of the last-processed string */
60217309Snwhitehorn    int *list;			/* indices into the string */
61217309Snwhitehorn} CACHE;
62217309Snwhitehorn
63217309Snwhitehorn#if USE_CACHING
64217309Snwhitehorn#define SAME_CACHE(c,s,l) (c->string != 0 && memcmp(c->string,s,l) == 0)
65217309Snwhitehorn
66217309Snwhitehornstatic CACHE *cache_list;
67217309Snwhitehorn
68255852Sdtesketypedef enum {
69255852Sdteske    cInxCols
70255852Sdteske    ,cCntWideBytes
71255852Sdteske    ,cCntWideChars
72255852Sdteske    ,cInxWideChars
73255852Sdteske    ,cMAX
74255852Sdteske} CACHE_USED;
75255852Sdteske
76217309Snwhitehorn#ifdef HAVE_TSEARCH
77217309Snwhitehornstatic void *sorted_cache;
78217309Snwhitehorn#endif
79217309Snwhitehorn
80217309Snwhitehorn#ifdef USE_WIDE_CURSES
81217309Snwhitehornstatic int
82217309Snwhitehornhave_locale(void)
83217309Snwhitehorn{
84217309Snwhitehorn    static int result = -1;
85217309Snwhitehorn    if (result < 0) {
86217309Snwhitehorn	char *test = setlocale(LC_ALL, 0);
87217309Snwhitehorn	if (test == 0 || *test == 0) {
88217309Snwhitehorn	    result = FALSE;
89217309Snwhitehorn	} else if (strcmp(test, "C") && strcmp(test, "POSIX")) {
90217309Snwhitehorn	    result = TRUE;
91217309Snwhitehorn	} else {
92217309Snwhitehorn	    result = FALSE;
93217309Snwhitehorn	}
94217309Snwhitehorn    }
95217309Snwhitehorn    return result;
96217309Snwhitehorn}
97217309Snwhitehorn#endif
98217309Snwhitehorn
99217309Snwhitehorn#ifdef HAVE_TSEARCH
100255852Sdteske
101255852Sdteske#if 0
102255852Sdteskestatic void
103255852Sdteskeshow_tsearch(const void *nodep, const VISIT which, const int depth)
104255852Sdteske{
105255852Sdteske    const CACHE *p = *(CACHE * const *) nodep;
106255852Sdteske    (void) depth;
107255852Sdteske    if (which == postorder || which == leaf) {
108255852Sdteske	dlg_trace_msg("\tcache %p %p:%s\n", p, p->string, p->string);
109255852Sdteske    }
110255852Sdteske}
111255852Sdteske
112255852Sdteskestatic void
113255852Sdtesketrace_cache(const char *fn, int ln)
114255852Sdteske{
115255852Sdteske    dlg_trace_msg("trace_cache %s@%d\n", fn, ln);
116255852Sdteske    twalk(sorted_cache, show_tsearch);
117255852Sdteske}
118255852Sdteske
119255852Sdteske#else
120255852Sdteske#define trace_cache(fn, ln)	/* nothing */
121255852Sdteske#endif
122255852Sdteske
123217309Snwhitehornstatic int
124217309Snwhitehorncompare_cache(const void *a, const void *b)
125217309Snwhitehorn{
126217309Snwhitehorn    const CACHE *p = (const CACHE *) a;
127217309Snwhitehorn    const CACHE *q = (const CACHE *) b;
128255852Sdteske    int result = (p->cache_num - q->cache_num);
129217309Snwhitehorn    if (result == 0)
130220749Snwhitehorn	result = (int) (p->string_at - q->string_at);
131217309Snwhitehorn    return result;
132217309Snwhitehorn}
133217309Snwhitehorn#endif
134217309Snwhitehorn
135217309Snwhitehornstatic CACHE *
136255852Sdteskefind_cache(int cache_num, const char *string)
137217309Snwhitehorn{
138217309Snwhitehorn    CACHE *p;
139217309Snwhitehorn
140217309Snwhitehorn#ifdef HAVE_TSEARCH
141217309Snwhitehorn    void *pp;
142217309Snwhitehorn    CACHE find;
143217309Snwhitehorn
144217309Snwhitehorn    memset(&find, 0, sizeof(find));
145255852Sdteske    find.cache_num = cache_num;
146217309Snwhitehorn    find.string_at = string;
147217309Snwhitehorn
148217309Snwhitehorn    if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) {
149217309Snwhitehorn	p = *(CACHE **) pp;
150217309Snwhitehorn    } else {
151217309Snwhitehorn	p = 0;
152217309Snwhitehorn    }
153217309Snwhitehorn#else
154217309Snwhitehorn    for (p = cache_list; p != 0; p = p->next) {
155255852Sdteske	if (p->string_at == string) {
156217309Snwhitehorn	    break;
157217309Snwhitehorn	}
158217309Snwhitehorn    }
159217309Snwhitehorn#endif
160217309Snwhitehorn    return p;
161217309Snwhitehorn}
162217309Snwhitehorn
163255852Sdteskestatic CACHE *
164255852Sdteskemake_cache(int cache_num, const char *string)
165217309Snwhitehorn{
166217309Snwhitehorn    CACHE *p;
167217309Snwhitehorn
168217309Snwhitehorn    p = dlg_calloc(CACHE, 1);
169217309Snwhitehorn    assert_ptr(p, "load_cache");
170217309Snwhitehorn    p->next = cache_list;
171217309Snwhitehorn    cache_list = p;
172217309Snwhitehorn
173255852Sdteske    p->cache_num = cache_num;
174217309Snwhitehorn    p->string_at = string;
175217309Snwhitehorn
176217309Snwhitehorn#ifdef HAVE_TSEARCH
177217309Snwhitehorn    (void) tsearch(p, &sorted_cache, compare_cache);
178217309Snwhitehorn#endif
179255852Sdteske    return p;
180217309Snwhitehorn}
181217309Snwhitehorn
182255852Sdteskestatic CACHE *
183255852Sdteskeload_cache(int cache_num, const char *string)
184217309Snwhitehorn{
185217309Snwhitehorn    CACHE *p;
186217309Snwhitehorn
187255852Sdteske    if ((p = find_cache(cache_num, string)) == 0) {
188255852Sdteske	p = make_cache(cache_num, string);
189217309Snwhitehorn    }
190255852Sdteske    return p;
191217309Snwhitehorn}
192217309Snwhitehorn#else
193255852Sdteskestatic CACHE my_cache;
194217309Snwhitehorn#define SAME_CACHE(c,s,l) (c->string != 0)
195255852Sdteske#define load_cache(cache, string) &my_cache
196255852Sdteske#endif /* USE_CACHING */
197217309Snwhitehorn
198217309Snwhitehorn/*
199217309Snwhitehorn * If the given string has not changed, we do not need to update the index.
200217309Snwhitehorn * If we need to update the index, allocate enough memory for it.
201217309Snwhitehorn */
202217309Snwhitehornstatic bool
203217309Snwhitehornsame_cache2(CACHE * cache, const char *string, unsigned i_len)
204217309Snwhitehorn{
205217309Snwhitehorn    unsigned need;
206220749Snwhitehorn    size_t s_len = strlen(string);
207255852Sdteske    bool result = TRUE;
208217309Snwhitehorn
209255852Sdteske    if (cache->s_len == 0
210255852Sdteske	|| cache->s_len < s_len
211255852Sdteske	|| cache->list == 0
212255852Sdteske	|| !SAME_CACHE(cache, string, (size_t) s_len)) {
213217309Snwhitehorn
214255852Sdteske	need = (i_len + 1);
215255852Sdteske	if (cache->list == 0) {
216255852Sdteske	    cache->list = dlg_malloc(int, need);
217255852Sdteske	} else if (cache->i_len < i_len) {
218255852Sdteske	    cache->list = dlg_realloc(int, need, cache->list);
219255852Sdteske	}
220255852Sdteske	assert_ptr(cache->list, "load_cache");
221255852Sdteske	cache->i_len = i_len;
222217309Snwhitehorn
223255852Sdteske	if (cache->s_len >= s_len && cache->string != 0) {
224255852Sdteske	    strcpy(cache->string, string);
225255852Sdteske	} else {
226255852Sdteske	    if (cache->string != 0)
227255852Sdteske		free(cache->string);
228255852Sdteske	    cache->string = dlg_strclone(string);
229255852Sdteske	}
230255852Sdteske	cache->s_len = s_len;
231255852Sdteske
232255852Sdteske	result = FALSE;
233217309Snwhitehorn    }
234255852Sdteske    return result;
235217309Snwhitehorn}
236217309Snwhitehorn
237217309Snwhitehorn#ifdef USE_WIDE_CURSES
238217309Snwhitehorn/*
239217309Snwhitehorn * Like same_cache2(), but we are only concerned about caching a copy of the
240217309Snwhitehorn * string and its associated length.
241217309Snwhitehorn */
242217309Snwhitehornstatic bool
243220749Snwhitehornsame_cache1(CACHE * cache, const char *string, size_t i_len)
244217309Snwhitehorn{
245220749Snwhitehorn    size_t s_len = strlen(string);
246255852Sdteske    bool result = TRUE;
247217309Snwhitehorn
248255852Sdteske    if (cache->s_len != s_len
249255852Sdteske	|| !SAME_CACHE(cache, string, (size_t) s_len)) {
250217309Snwhitehorn
251255852Sdteske	if (cache->s_len >= s_len && cache->string != 0) {
252255852Sdteske	    strcpy(cache->string, string);
253255852Sdteske	} else {
254255852Sdteske	    if (cache->string != 0)
255255852Sdteske		free(cache->string);
256255852Sdteske	    cache->string = dlg_strclone(string);
257255852Sdteske	}
258255852Sdteske	cache->s_len = s_len;
259255852Sdteske	cache->i_len = i_len;
260255852Sdteske
261255852Sdteske	result = FALSE;
262217309Snwhitehorn    }
263255852Sdteske    return result;
264217309Snwhitehorn}
265217309Snwhitehorn#endif /* USE_CACHING */
266217309Snwhitehorn
267217309Snwhitehorn/*
268217309Snwhitehorn * Counts the number of bytes that make up complete wide-characters, up to byte
269217309Snwhitehorn * 'len'.  If there is no locale set, simply return the original length.
270217309Snwhitehorn */
271217309Snwhitehorn#ifdef USE_WIDE_CURSES
272217309Snwhitehornstatic int
273217309Snwhitehorndlg_count_wcbytes(const char *string, size_t len)
274217309Snwhitehorn{
275217309Snwhitehorn    int result;
276217309Snwhitehorn
277217309Snwhitehorn    if (have_locale()) {
278255852Sdteske	CACHE *cache = load_cache(cCntWideBytes, string);
279255852Sdteske	if (!same_cache1(cache, string, len)) {
280217309Snwhitehorn	    while (len != 0) {
281217309Snwhitehorn		size_t code = 0;
282255852Sdteske		const char *src = cache->string;
283217309Snwhitehorn		mbstate_t state;
284255852Sdteske		char save = cache->string[len];
285217309Snwhitehorn
286255852Sdteske		cache->string[len] = '\0';
287217309Snwhitehorn		memset(&state, 0, sizeof(state));
288217309Snwhitehorn		code = mbsrtowcs((wchar_t *) 0, &src, len, &state);
289255852Sdteske		cache->string[len] = save;
290217309Snwhitehorn		if ((int) code >= 0) {
291217309Snwhitehorn		    break;
292217309Snwhitehorn		}
293217309Snwhitehorn		--len;
294217309Snwhitehorn	    }
295255852Sdteske	    cache->i_len = len;
296217309Snwhitehorn	}
297255852Sdteske	result = (int) cache->i_len;
298217309Snwhitehorn    } else {
299217309Snwhitehorn	result = (int) len;
300217309Snwhitehorn    }
301217309Snwhitehorn    return result;
302217309Snwhitehorn}
303217309Snwhitehorn#endif /* USE_WIDE_CURSES */
304217309Snwhitehorn
305217309Snwhitehorn/*
306217309Snwhitehorn * Counts the number of wide-characters in the string.
307217309Snwhitehorn */
308217309Snwhitehornint
309217309Snwhitehorndlg_count_wchars(const char *string)
310217309Snwhitehorn{
311217309Snwhitehorn    int result;
312255852Sdteske#ifdef USE_WIDE_CURSES
313217309Snwhitehorn
314217309Snwhitehorn    if (have_locale()) {
315217309Snwhitehorn	size_t len = strlen(string);
316255852Sdteske	CACHE *cache = load_cache(cCntWideChars, string);
317217309Snwhitehorn
318255852Sdteske	if (!same_cache1(cache, string, len)) {
319255852Sdteske	    const char *src = cache->string;
320217309Snwhitehorn	    mbstate_t state;
321255852Sdteske	    int part = dlg_count_wcbytes(cache->string, len);
322255852Sdteske	    char save = cache->string[part];
323217309Snwhitehorn	    size_t code;
324217309Snwhitehorn	    wchar_t *temp = dlg_calloc(wchar_t, len + 1);
325217309Snwhitehorn
326251843Sbapt	    if (temp != 0) {
327255852Sdteske		cache->string[part] = '\0';
328251843Sbapt		memset(&state, 0, sizeof(state));
329251843Sbapt		code = mbsrtowcs(temp, &src, (size_t) part, &state);
330255852Sdteske		cache->i_len = ((int) code >= 0) ? wcslen(temp) : 0;
331255852Sdteske		cache->string[part] = save;
332251843Sbapt		free(temp);
333251843Sbapt	    } else {
334255852Sdteske		cache->i_len = 0;
335251843Sbapt	    }
336217309Snwhitehorn	}
337255852Sdteske	result = (int) cache->i_len;
338217309Snwhitehorn    } else
339217309Snwhitehorn#endif /* USE_WIDE_CURSES */
340217309Snwhitehorn    {
341217309Snwhitehorn	result = (int) strlen(string);
342217309Snwhitehorn    }
343217309Snwhitehorn    return result;
344217309Snwhitehorn}
345217309Snwhitehorn
346217309Snwhitehorn/*
347217309Snwhitehorn * Build an index of the wide-characters in the string, so we can easily tell
348217309Snwhitehorn * which byte-offset begins a given wide-character.
349217309Snwhitehorn */
350217309Snwhitehornconst int *
351217309Snwhitehorndlg_index_wchars(const char *string)
352217309Snwhitehorn{
353217309Snwhitehorn    unsigned len = (unsigned) dlg_count_wchars(string);
354217309Snwhitehorn    unsigned inx;
355255852Sdteske    CACHE *cache = load_cache(cInxWideChars, string);
356217309Snwhitehorn
357255852Sdteske    if (!same_cache2(cache, string, len)) {
358217309Snwhitehorn	const char *current = string;
359217309Snwhitehorn
360255852Sdteske	cache->list[0] = 0;
361217309Snwhitehorn	for (inx = 1; inx <= len; ++inx) {
362217309Snwhitehorn#ifdef USE_WIDE_CURSES
363217309Snwhitehorn	    if (have_locale()) {
364217309Snwhitehorn		mbstate_t state;
365217309Snwhitehorn		int width;
366217309Snwhitehorn		memset(&state, 0, sizeof(state));
367217309Snwhitehorn		width = (int) mbrlen(current, strlen(current), &state);
368217309Snwhitehorn		if (width <= 0)
369217309Snwhitehorn		    width = 1;	/* FIXME: what if we have a control-char? */
370217309Snwhitehorn		current += width;
371255852Sdteske		cache->list[inx] = cache->list[inx - 1] + width;
372217309Snwhitehorn	    } else
373217309Snwhitehorn#endif /* USE_WIDE_CURSES */
374217309Snwhitehorn	    {
375217309Snwhitehorn		(void) current;
376255852Sdteske		cache->list[inx] = (int) inx;
377217309Snwhitehorn	    }
378217309Snwhitehorn	}
379217309Snwhitehorn    }
380255852Sdteske    return cache->list;
381217309Snwhitehorn}
382217309Snwhitehorn
383217309Snwhitehorn/*
384217309Snwhitehorn * Given the character-offset to find in the list, return the corresponding
385217309Snwhitehorn * array index.
386217309Snwhitehorn */
387217309Snwhitehornint
388217309Snwhitehorndlg_find_index(const int *list, int limit, int to_find)
389217309Snwhitehorn{
390217309Snwhitehorn    int result;
391217309Snwhitehorn    for (result = 0; result <= limit; ++result) {
392217309Snwhitehorn	if (to_find == list[result]
393217309Snwhitehorn	    || result == limit
394251843Sbapt	    || ((result < limit) && (to_find < list[result + 1]))) {
395217309Snwhitehorn	    break;
396251843Sbapt	}
397217309Snwhitehorn    }
398217309Snwhitehorn    return result;
399217309Snwhitehorn}
400217309Snwhitehorn
401217309Snwhitehorn/*
402217309Snwhitehorn * Build a list of the display-columns for the given string's characters.
403217309Snwhitehorn */
404217309Snwhitehornconst int *
405217309Snwhitehorndlg_index_columns(const char *string)
406217309Snwhitehorn{
407217309Snwhitehorn    unsigned len = (unsigned) dlg_count_wchars(string);
408217309Snwhitehorn    unsigned inx;
409255852Sdteske    CACHE *cache = load_cache(cInxCols, string);
410217309Snwhitehorn
411255852Sdteske    if (!same_cache2(cache, string, len)) {
412255852Sdteske	cache->list[0] = 0;
413217309Snwhitehorn#ifdef USE_WIDE_CURSES
414217309Snwhitehorn	if (have_locale()) {
415217309Snwhitehorn	    size_t num_bytes = strlen(string);
416217309Snwhitehorn	    const int *inx_wchars = dlg_index_wchars(string);
417217309Snwhitehorn	    mbstate_t state;
418217309Snwhitehorn
419217309Snwhitehorn	    for (inx = 0; inx < len; ++inx) {
420217309Snwhitehorn		wchar_t temp[2];
421217309Snwhitehorn		size_t check;
422217309Snwhitehorn		int result;
423217309Snwhitehorn
424217309Snwhitehorn		if (string[inx_wchars[inx]] == TAB) {
425255852Sdteske		    result = ((cache->list[inx] | 7) + 1) - cache->list[inx];
426217309Snwhitehorn		} else {
427217309Snwhitehorn		    memset(&state, 0, sizeof(state));
428217309Snwhitehorn		    memset(temp, 0, sizeof(temp));
429217309Snwhitehorn		    check = mbrtowc(temp,
430217309Snwhitehorn				    string + inx_wchars[inx],
431217309Snwhitehorn				    num_bytes - (size_t) inx_wchars[inx],
432217309Snwhitehorn				    &state);
433217309Snwhitehorn		    if ((int) check <= 0) {
434217309Snwhitehorn			result = 1;
435217309Snwhitehorn		    } else {
436217309Snwhitehorn			result = wcwidth(temp[0]);
437217309Snwhitehorn		    }
438217309Snwhitehorn		    if (result < 0) {
439220749Snwhitehorn			const wchar_t *printable;
440220749Snwhitehorn			cchar_t temp2, *temp2p = &temp2;
441220749Snwhitehorn			setcchar(temp2p, temp, 0, 0, 0);
442220749Snwhitehorn			printable = wunctrl(temp2p);
443217309Snwhitehorn			result = printable ? (int) wcslen(printable) : 1;
444217309Snwhitehorn		    }
445217309Snwhitehorn		}
446255852Sdteske		cache->list[inx + 1] = result;
447217309Snwhitehorn		if (inx != 0)
448255852Sdteske		    cache->list[inx + 1] += cache->list[inx];
449217309Snwhitehorn	    }
450217309Snwhitehorn	} else
451217309Snwhitehorn#endif /* USE_WIDE_CURSES */
452217309Snwhitehorn	{
453217309Snwhitehorn	    for (inx = 0; inx < len; ++inx) {
454217309Snwhitehorn		chtype ch = UCH(string[inx]);
455217309Snwhitehorn
456217309Snwhitehorn		if (ch == TAB)
457255852Sdteske		    cache->list[inx + 1] =
458255852Sdteske			((cache->list[inx] | 7) + 1) - cache->list[inx];
459217309Snwhitehorn		else if (isprint(ch))
460255852Sdteske		    cache->list[inx + 1] = 1;
461217309Snwhitehorn		else {
462217309Snwhitehorn		    const char *printable;
463217309Snwhitehorn		    printable = unctrl(ch);
464255852Sdteske		    cache->list[inx + 1] = (printable
465255852Sdteske					    ? (int) strlen(printable)
466255852Sdteske					    : 1);
467217309Snwhitehorn		}
468217309Snwhitehorn		if (inx != 0)
469255852Sdteske		    cache->list[inx + 1] += cache->list[inx];
470217309Snwhitehorn	    }
471217309Snwhitehorn	}
472217309Snwhitehorn    }
473255852Sdteske    return cache->list;
474217309Snwhitehorn}
475217309Snwhitehorn
476217309Snwhitehorn/*
477217309Snwhitehorn * Returns the number of columns used for a string.  That happens to be the
478217309Snwhitehorn * end-value of the cols[] array.
479217309Snwhitehorn */
480217309Snwhitehornint
481217309Snwhitehorndlg_count_columns(const char *string)
482217309Snwhitehorn{
483217309Snwhitehorn    int result = 0;
484217309Snwhitehorn    int limit = dlg_count_wchars(string);
485217309Snwhitehorn    if (limit > 0) {
486217309Snwhitehorn	const int *cols = dlg_index_columns(string);
487217309Snwhitehorn	result = cols[limit];
488217309Snwhitehorn    } else {
489217309Snwhitehorn	result = (int) strlen(string);
490217309Snwhitehorn    }
491255852Sdteske    dlg_finish_string(string);
492217309Snwhitehorn    return result;
493217309Snwhitehorn}
494217309Snwhitehorn
495217309Snwhitehorn/*
496217309Snwhitehorn * Given a column limit, count the number of wide characters that can fit
497217309Snwhitehorn * into that limit.  The offset is used to skip over a leading character
498217309Snwhitehorn * that was already written.
499217309Snwhitehorn */
500217309Snwhitehornint
501217309Snwhitehorndlg_limit_columns(const char *string, int limit, int offset)
502217309Snwhitehorn{
503217309Snwhitehorn    const int *cols = dlg_index_columns(string);
504217309Snwhitehorn    int result = dlg_count_wchars(string);
505217309Snwhitehorn
506217309Snwhitehorn    while (result > 0 && (cols[result] - cols[offset]) > limit)
507217309Snwhitehorn	--result;
508217309Snwhitehorn    return result;
509217309Snwhitehorn}
510217309Snwhitehorn
511217309Snwhitehorn/*
512217309Snwhitehorn * Updates the string and character-offset, given various editing characters
513217309Snwhitehorn * or literal characters which are inserted at the character-offset.
514217309Snwhitehorn */
515217309Snwhitehornbool
516217309Snwhitehorndlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
517217309Snwhitehorn{
518217309Snwhitehorn    int i;
519217309Snwhitehorn    int len = (int) strlen(string);
520217309Snwhitehorn    int limit = dlg_count_wchars(string);
521217309Snwhitehorn    const int *indx = dlg_index_wchars(string);
522217309Snwhitehorn    int offset = dlg_find_index(indx, limit, *chr_offset);
523217309Snwhitehorn    int max_len = dlg_max_input(MAX_LEN);
524217309Snwhitehorn    bool edit = TRUE;
525217309Snwhitehorn
526217309Snwhitehorn    /* transform editing characters into equivalent function-keys */
527217309Snwhitehorn    if (!fkey) {
528217309Snwhitehorn	fkey = TRUE;		/* assume we transform */
529217309Snwhitehorn	switch (key) {
530217309Snwhitehorn	case 0:
531217309Snwhitehorn	    break;
532217309Snwhitehorn	case ESC:
533217309Snwhitehorn	case TAB:
534217309Snwhitehorn	    fkey = FALSE;	/* this is used for navigation */
535217309Snwhitehorn	    break;
536217309Snwhitehorn	default:
537217309Snwhitehorn	    fkey = FALSE;	/* ...no, we did not transform */
538217309Snwhitehorn	    break;
539217309Snwhitehorn	}
540217309Snwhitehorn    }
541217309Snwhitehorn
542217309Snwhitehorn    if (fkey) {
543217309Snwhitehorn	switch (key) {
544217309Snwhitehorn	case 0:		/* special case for loop entry */
545217309Snwhitehorn	    edit = force;
546217309Snwhitehorn	    break;
547217309Snwhitehorn	case DLGK_GRID_LEFT:
548251843Sbapt	    if (*chr_offset && offset > 0)
549217309Snwhitehorn		*chr_offset = indx[offset - 1];
550217309Snwhitehorn	    break;
551217309Snwhitehorn	case DLGK_GRID_RIGHT:
552217309Snwhitehorn	    if (offset < limit)
553217309Snwhitehorn		*chr_offset = indx[offset + 1];
554217309Snwhitehorn	    break;
555217309Snwhitehorn	case DLGK_BEGIN:
556217309Snwhitehorn	    if (*chr_offset)
557217309Snwhitehorn		*chr_offset = 0;
558217309Snwhitehorn	    break;
559217309Snwhitehorn	case DLGK_FINAL:
560217309Snwhitehorn	    if (offset < limit)
561217309Snwhitehorn		*chr_offset = indx[limit];
562217309Snwhitehorn	    break;
563217309Snwhitehorn	case DLGK_DELETE_LEFT:
564217309Snwhitehorn	    if (offset) {
565217309Snwhitehorn		int gap = indx[offset] - indx[offset - 1];
566217309Snwhitehorn		*chr_offset = indx[offset - 1];
567217309Snwhitehorn		if (gap > 0) {
568217309Snwhitehorn		    for (i = *chr_offset;
569217309Snwhitehorn			 (string[i] = string[i + gap]) != '\0';
570217309Snwhitehorn			 i++) {
571217309Snwhitehorn			;
572217309Snwhitehorn		    }
573217309Snwhitehorn		}
574217309Snwhitehorn	    }
575217309Snwhitehorn	    break;
576217309Snwhitehorn	case DLGK_DELETE_RIGHT:
577217309Snwhitehorn	    if (limit) {
578217309Snwhitehorn		if (--limit == 0) {
579217309Snwhitehorn		    string[*chr_offset = 0] = '\0';
580217309Snwhitehorn		} else {
581217309Snwhitehorn		    int gap = ((offset <= limit)
582217309Snwhitehorn			       ? (indx[offset + 1] - indx[offset])
583217309Snwhitehorn			       : 0);
584217309Snwhitehorn		    if (gap > 0) {
585217309Snwhitehorn			for (i = indx[offset];
586217309Snwhitehorn			     (string[i] = string[i + gap]) != '\0';
587217309Snwhitehorn			     i++) {
588217309Snwhitehorn			    ;
589217309Snwhitehorn			}
590217309Snwhitehorn		    } else if (offset > 0) {
591217309Snwhitehorn			string[indx[offset - 1]] = '\0';
592217309Snwhitehorn		    }
593217309Snwhitehorn		    if (*chr_offset > indx[limit])
594217309Snwhitehorn			*chr_offset = indx[limit];
595217309Snwhitehorn		}
596217309Snwhitehorn	    }
597217309Snwhitehorn	    break;
598217309Snwhitehorn	case DLGK_DELETE_ALL:
599217309Snwhitehorn	    string[*chr_offset = 0] = '\0';
600217309Snwhitehorn	    break;
601217309Snwhitehorn	case DLGK_ENTER:
602217309Snwhitehorn	    edit = 0;
603217309Snwhitehorn	    break;
604217309Snwhitehorn#ifdef KEY_RESIZE
605217309Snwhitehorn	case KEY_RESIZE:
606217309Snwhitehorn	    edit = 0;
607217309Snwhitehorn	    break;
608217309Snwhitehorn#endif
609217309Snwhitehorn	case DLGK_GRID_UP:
610217309Snwhitehorn	case DLGK_GRID_DOWN:
611217309Snwhitehorn	case DLGK_FIELD_NEXT:
612217309Snwhitehorn	case DLGK_FIELD_PREV:
613217309Snwhitehorn	    edit = 0;
614217309Snwhitehorn	    break;
615217309Snwhitehorn	case ERR:
616217309Snwhitehorn	    edit = 0;
617217309Snwhitehorn	    break;
618217309Snwhitehorn	default:
619217309Snwhitehorn	    beep();
620217309Snwhitehorn	    break;
621217309Snwhitehorn	}
622217309Snwhitehorn    } else {
623217309Snwhitehorn	if (key == ESC || key == ERR) {
624217309Snwhitehorn	    edit = 0;
625217309Snwhitehorn	} else {
626217309Snwhitehorn	    if (len < max_len) {
627217309Snwhitehorn		for (i = ++len; i > *chr_offset; i--)
628217309Snwhitehorn		    string[i] = string[i - 1];
629217309Snwhitehorn		string[*chr_offset] = (char) key;
630217309Snwhitehorn		*chr_offset += 1;
631217309Snwhitehorn	    } else {
632217309Snwhitehorn		(void) beep();
633217309Snwhitehorn	    }
634217309Snwhitehorn	}
635217309Snwhitehorn    }
636217309Snwhitehorn    return edit;
637217309Snwhitehorn}
638217309Snwhitehorn
639217309Snwhitehornstatic void
640217309Snwhitehorncompute_edit_offset(const char *string,
641217309Snwhitehorn		    int chr_offset,
642217309Snwhitehorn		    int x_last,
643217309Snwhitehorn		    int *p_dpy_column,
644217309Snwhitehorn		    int *p_scroll_amt)
645217309Snwhitehorn{
646217309Snwhitehorn    const int *cols = dlg_index_columns(string);
647217309Snwhitehorn    const int *indx = dlg_index_wchars(string);
648217309Snwhitehorn    int limit = dlg_count_wchars(string);
649217309Snwhitehorn    int offset = dlg_find_index(indx, limit, chr_offset);
650217309Snwhitehorn    int offset2;
651217309Snwhitehorn    int dpy_column;
652217309Snwhitehorn    int n;
653217309Snwhitehorn
654217309Snwhitehorn    for (n = offset2 = 0; n <= offset; ++n) {
655217309Snwhitehorn	if ((cols[offset] - cols[n]) < x_last
656217309Snwhitehorn	    && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
657217309Snwhitehorn	    offset2 = n;
658217309Snwhitehorn	    break;
659217309Snwhitehorn	}
660217309Snwhitehorn    }
661217309Snwhitehorn
662217309Snwhitehorn    dpy_column = cols[offset] - cols[offset2];
663217309Snwhitehorn
664217309Snwhitehorn    if (p_dpy_column != 0)
665217309Snwhitehorn	*p_dpy_column = dpy_column;
666217309Snwhitehorn    if (p_scroll_amt != 0)
667217309Snwhitehorn	*p_scroll_amt = offset2;
668217309Snwhitehorn}
669217309Snwhitehorn
670217309Snwhitehorn/*
671217309Snwhitehorn * Given the character-offset in the string, returns the display-offset where
672217309Snwhitehorn * we will position the cursor.
673217309Snwhitehorn */
674217309Snwhitehornint
675217309Snwhitehorndlg_edit_offset(char *string, int chr_offset, int x_last)
676217309Snwhitehorn{
677217309Snwhitehorn    int result;
678217309Snwhitehorn
679217309Snwhitehorn    compute_edit_offset(string, chr_offset, x_last, &result, 0);
680217309Snwhitehorn
681217309Snwhitehorn    return result;
682217309Snwhitehorn}
683217309Snwhitehorn
684217309Snwhitehorn/*
685217309Snwhitehorn * Displays the string, shifted as necessary, to fit within the box and show
686217309Snwhitehorn * the current character-offset.
687217309Snwhitehorn */
688217309Snwhitehornvoid
689217309Snwhitehorndlg_show_string(WINDOW *win,
690217309Snwhitehorn		const char *string,	/* string to display (may be multibyte) */
691217309Snwhitehorn		int chr_offset,	/* character (not bytes) offset */
692217309Snwhitehorn		chtype attr,	/* window-attributes */
693217309Snwhitehorn		int y_base,	/* beginning row on screen */
694217309Snwhitehorn		int x_base,	/* beginning column on screen */
695217309Snwhitehorn		int x_last,	/* number of columns on screen */
696217309Snwhitehorn		bool hidden,	/* if true, do not echo */
697217309Snwhitehorn		bool force)	/* if true, force repaint */
698217309Snwhitehorn{
699217309Snwhitehorn    x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
700217309Snwhitehorn
701217309Snwhitehorn    if (hidden && !dialog_vars.insecure) {
702217309Snwhitehorn	if (force) {
703217309Snwhitehorn	    (void) wmove(win, y_base, x_base);
704217309Snwhitehorn	    wrefresh(win);
705217309Snwhitehorn	}
706217309Snwhitehorn    } else {
707217309Snwhitehorn	const int *cols = dlg_index_columns(string);
708217309Snwhitehorn	const int *indx = dlg_index_wchars(string);
709217309Snwhitehorn	int limit = dlg_count_wchars(string);
710217309Snwhitehorn
711217309Snwhitehorn	int i, j, k;
712217309Snwhitehorn	int input_x;
713217309Snwhitehorn	int scrollamt;
714217309Snwhitehorn
715217309Snwhitehorn	compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
716217309Snwhitehorn
717251843Sbapt	(void) wattrset(win, attr);
718217309Snwhitehorn	(void) wmove(win, y_base, x_base);
719217309Snwhitehorn	for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
720217309Snwhitehorn	    int check = cols[i + 1] - cols[scrollamt];
721217309Snwhitehorn	    if (check <= x_last) {
722217309Snwhitehorn		for (j = indx[i]; j < indx[i + 1]; ++j) {
723217309Snwhitehorn		    chtype ch = UCH(string[j]);
724217309Snwhitehorn		    if (hidden && dialog_vars.insecure) {
725217309Snwhitehorn			waddch(win, '*');
726217309Snwhitehorn		    } else if (ch == TAB) {
727217309Snwhitehorn			int count = cols[i + 1] - cols[i];
728217309Snwhitehorn			while (--count >= 0)
729217309Snwhitehorn			    waddch(win, ' ');
730217309Snwhitehorn		    } else {
731217309Snwhitehorn			waddch(win, ch);
732217309Snwhitehorn		    }
733217309Snwhitehorn		}
734217309Snwhitehorn		k = check;
735217309Snwhitehorn	    } else {
736217309Snwhitehorn		break;
737217309Snwhitehorn	    }
738217309Snwhitehorn	}
739217309Snwhitehorn	while (k++ < x_last)
740217309Snwhitehorn	    waddch(win, ' ');
741217309Snwhitehorn	(void) wmove(win, y_base, x_base + input_x);
742217309Snwhitehorn	wrefresh(win);
743217309Snwhitehorn    }
744217309Snwhitehorn}
745217309Snwhitehorn
746255852Sdteske/*
747255852Sdteske * Discard cached data for the given string.
748255852Sdteske */
749255852Sdteskevoid
750255852Sdteskedlg_finish_string(const char *string)
751255852Sdteske{
752255852Sdteske#if USE_CACHING
753255852Sdteske    if ((string != 0) && dialog_state.finish_string) {
754255852Sdteske	CACHE *p = cache_list;
755255852Sdteske	CACHE *q = 0;
756255852Sdteske	CACHE *r;
757255852Sdteske
758255852Sdteske	while (p != 0) {
759255852Sdteske	    if (p->string_at == string) {
760255852Sdteske#ifdef HAVE_TSEARCH
761255852Sdteske		if (tdelete(p, &sorted_cache, compare_cache) == 0) {
762255852Sdteske		    continue;
763255852Sdteske		}
764255852Sdteske		trace_cache(__FILE__, __LINE__);
765255852Sdteske#endif
766255852Sdteske		if (p->string != 0)
767255852Sdteske		    free(p->string);
768255852Sdteske		if (p->list != 0)
769255852Sdteske		    free(p->list);
770255852Sdteske		if (p == cache_list) {
771255852Sdteske		    cache_list = p->next;
772255852Sdteske		    r = cache_list;
773255852Sdteske		} else {
774255852Sdteske		    q->next = p->next;
775255852Sdteske		    r = q;
776255852Sdteske		}
777255852Sdteske		free(p);
778255852Sdteske		p = r;
779255852Sdteske	    } else {
780255852Sdteske		q = p;
781255852Sdteske		p = p->next;
782255852Sdteske	    }
783255852Sdteske	}
784255852Sdteske    }
785255852Sdteske#else
786255852Sdteske    (void) string;
787255852Sdteske#endif
788255852Sdteske}
789255852Sdteske
790217309Snwhitehorn#ifdef NO_LEAKS
791217309Snwhitehornvoid
792217309Snwhitehorn_dlg_inputstr_leaks(void)
793217309Snwhitehorn{
794217309Snwhitehorn#if USE_CACHING
795255852Sdteske    dialog_state.finish_string = TRUE;
796255852Sdteske    trace_cache(__FILE__, __LINE__);
797217309Snwhitehorn    while (cache_list != 0) {
798255852Sdteske	dlg_finish_string(cache_list->string_at);
799217309Snwhitehorn    }
800217309Snwhitehorn#endif /* USE_CACHING */
801217309Snwhitehorn}
802217309Snwhitehorn#endif /* NO_LEAKS */
803