freebsd-dev/contrib/dialog/inputstr.c
Nathan Whitehorn 1971864966 Revert r241818 that updated dialog to 20120706. This turns out to horribly
break mixed form dialogs in conjunction with the FreeBSD termcap, making
the bsdinstall partition editor Add dialog, among other things, completely
nonfunctional. This restores dialog 20110707.
2012-12-30 04:22:34 +00:00

752 lines
17 KiB
C

/*
* $Id: inputstr.c,v 1.69 2011/01/16 21:52:35 tom Exp $
*
* inputstr.c -- functions for input/display of a string
*
* Copyright 2000-2010,2011 Thomas E. Dickey
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License, version 2.1
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to
* Free Software Foundation, Inc.
* 51 Franklin St., Fifth Floor
* Boston, MA 02110, USA.
*/
#include <dialog.h>
#include <dlg_keys.h>
#include <errno.h>
#ifdef HAVE_SETLOCALE
#include <locale.h>
#endif
#if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
#include <search.h>
#else
#undef HAVE_TSEARCH
#endif
#ifdef NEED_WCHAR_H
#include <wchar.h>
#endif
#if defined(USE_WIDE_CURSES)
#define USE_CACHING 1
#elif defined(HAVE_XDIALOG)
#define USE_CACHING 1 /* editbox really needs caching! */
#else
#define USE_CACHING 0
#endif
typedef struct _cache {
struct _cache *next;
#if USE_CACHING
struct _cache *cache_at; /* unique: associate caches by CACHE */
const char *string_at; /* unique: associate caches by char* */
#endif
size_t s_len; /* strlen(string) - we add 1 for EOS */
size_t i_len; /* length(list) - we add 1 for EOS */
char *string; /* a copy of the last-processed string */
int *list; /* indices into the string */
} CACHE;
#if USE_CACHING
#define SAME_CACHE(c,s,l) (c->string != 0 && memcmp(c->string,s,l) == 0)
static CACHE *cache_list;
#ifdef HAVE_TSEARCH
static void *sorted_cache;
#endif
#ifdef USE_WIDE_CURSES
static int
have_locale(void)
{
static int result = -1;
if (result < 0) {
char *test = setlocale(LC_ALL, 0);
if (test == 0 || *test == 0) {
result = FALSE;
} else if (strcmp(test, "C") && strcmp(test, "POSIX")) {
result = TRUE;
} else {
result = FALSE;
}
}
return result;
}
#endif
#ifdef HAVE_TSEARCH
static int
compare_cache(const void *a, const void *b)
{
const CACHE *p = (const CACHE *) a;
const CACHE *q = (const CACHE *) b;
int result = 0;
result = (int) (p->cache_at - q->cache_at);
if (result == 0)
result = (int) (p->string_at - q->string_at);
return result;
}
#endif
static CACHE *
find_cache(CACHE * cache, const char *string)
{
CACHE *p;
#ifdef HAVE_TSEARCH
void *pp;
CACHE find;
memset(&find, 0, sizeof(find));
find.cache_at = cache;
find.string_at = string;
if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) {
p = *(CACHE **) pp;
} else {
p = 0;
}
#else
for (p = cache_list; p != 0; p = p->next) {
if (p->cache_at == cache
&& p->string_at == string) {
break;
}
}
#endif
return p;
}
static void
make_cache(CACHE * cache, const char *string)
{
CACHE *p;
p = dlg_calloc(CACHE, 1);
assert_ptr(p, "load_cache");
p->next = cache_list;
cache_list = p;
p->cache_at = cache;
p->string_at = string;
*cache = *p;
#ifdef HAVE_TSEARCH
(void) tsearch(p, &sorted_cache, compare_cache);
#endif
}
static void
load_cache(CACHE * cache, const char *string)
{
CACHE *p;
if ((p = find_cache(cache, string)) != 0) {
*cache = *p;
} else {
make_cache(cache, string);
}
}
static void
save_cache(CACHE * cache, const char *string)
{
CACHE *p;
if ((p = find_cache(cache, string)) != 0) {
CACHE *q = p->next;
*p = *cache;
p->next = q;
}
}
#else
#define SAME_CACHE(c,s,l) (c->string != 0)
#define load_cache(cache, string) /* nothing */
#define save_cache(cache, string) /* nothing */
#endif /* USE_WIDE_CURSES */
/*
* If the given string has not changed, we do not need to update the index.
* If we need to update the index, allocate enough memory for it.
*/
static bool
same_cache2(CACHE * cache, const char *string, unsigned i_len)
{
unsigned need;
size_t s_len = strlen(string);
if (cache->s_len != 0
&& cache->s_len >= s_len
&& cache->list != 0
&& SAME_CACHE(cache, string, (size_t) s_len)) {
return TRUE;
}
need = (i_len + 1);
if (cache->list == 0) {
cache->list = dlg_malloc(int, need);
} else if (cache->i_len < i_len) {
cache->list = dlg_realloc(int, need, cache->list);
}
cache->i_len = i_len;
if (cache->s_len >= s_len && cache->string != 0) {
strcpy(cache->string, string);
} else {
if (cache->string != 0)
free(cache->string);
cache->string = dlg_strclone(string);
}
cache->s_len = s_len;
return FALSE;
}
#ifdef USE_WIDE_CURSES
/*
* Like same_cache2(), but we are only concerned about caching a copy of the
* string and its associated length.
*/
static bool
same_cache1(CACHE * cache, const char *string, size_t i_len)
{
size_t s_len = strlen(string);
if (cache->s_len == s_len
&& SAME_CACHE(cache, string, (size_t) s_len)) {
return TRUE;
}
if (cache->s_len >= s_len && cache->string != 0) {
strcpy(cache->string, string);
} else {
if (cache->string != 0)
free(cache->string);
cache->string = dlg_strclone(string);
}
cache->s_len = s_len;
cache->i_len = i_len;
return FALSE;
}
#endif /* USE_CACHING */
/*
* Counts the number of bytes that make up complete wide-characters, up to byte
* 'len'. If there is no locale set, simply return the original length.
*/
#ifdef USE_WIDE_CURSES
static int
dlg_count_wcbytes(const char *string, size_t len)
{
int result;
if (have_locale()) {
static CACHE cache;
load_cache(&cache, string);
if (!same_cache1(&cache, string, len)) {
while (len != 0) {
int part = 0;
size_t code = 0;
const char *src = cache.string;
mbstate_t state;
char save = cache.string[len];
cache.string[len] = '\0';
memset(&state, 0, sizeof(state));
code = mbsrtowcs((wchar_t *) 0, &src, len, &state);
cache.string[len] = save;
if ((int) code >= 0) {
break;
}
++part;
--len;
}
cache.i_len = len;
save_cache(&cache, string);
}
result = (int) cache.i_len;
} else {
result = (int) len;
}
return result;
}
#endif /* USE_WIDE_CURSES */
/*
* Counts the number of wide-characters in the string.
*/
int
dlg_count_wchars(const char *string)
{
int result;
#ifdef USE_WIDE_CURSES
if (have_locale()) {
static CACHE cache;
size_t len = strlen(string);
load_cache(&cache, string);
if (!same_cache1(&cache, string, len)) {
const char *src = cache.string;
mbstate_t state;
int part = dlg_count_wcbytes(cache.string, len);
char save = cache.string[part];
size_t code;
wchar_t *temp = dlg_calloc(wchar_t, len + 1);
cache.string[part] = '\0';
memset(&state, 0, sizeof(state));
code = mbsrtowcs(temp, &src, (size_t) part, &state);
cache.i_len = ((int) code >= 0) ? wcslen(temp) : 0;
cache.string[part] = save;
free(temp);
save_cache(&cache, string);
}
result = (int) cache.i_len;
} else
#endif /* USE_WIDE_CURSES */
{
result = (int) strlen(string);
}
return result;
}
/*
* Build an index of the wide-characters in the string, so we can easily tell
* which byte-offset begins a given wide-character.
*/
const int *
dlg_index_wchars(const char *string)
{
static CACHE cache;
unsigned len = (unsigned) dlg_count_wchars(string);
unsigned inx;
load_cache(&cache, string);
if (!same_cache2(&cache, string, len)) {
const char *current = string;
cache.list[0] = 0;
for (inx = 1; inx <= len; ++inx) {
#ifdef USE_WIDE_CURSES
if (have_locale()) {
mbstate_t state;
int width;
memset(&state, 0, sizeof(state));
width = (int) mbrlen(current, strlen(current), &state);
if (width <= 0)
width = 1; /* FIXME: what if we have a control-char? */
current += width;
cache.list[inx] = cache.list[inx - 1] + width;
} else
#endif /* USE_WIDE_CURSES */
{
(void) current;
cache.list[inx] = (int) inx;
}
}
save_cache(&cache, string);
}
return cache.list;
}
/*
* Given the character-offset to find in the list, return the corresponding
* array index.
*/
int
dlg_find_index(const int *list, int limit, int to_find)
{
int result;
for (result = 0; result <= limit; ++result) {
if (to_find == list[result]
|| result == limit
|| to_find < list[result + 1])
break;
}
return result;
}
/*
* Build a list of the display-columns for the given string's characters.
*/
const int *
dlg_index_columns(const char *string)
{
static CACHE cache;
unsigned len = (unsigned) dlg_count_wchars(string);
unsigned inx;
load_cache(&cache, string);
if (!same_cache2(&cache, string, len)) {
cache.list[0] = 0;
#ifdef USE_WIDE_CURSES
if (have_locale()) {
size_t num_bytes = strlen(string);
const int *inx_wchars = dlg_index_wchars(string);
mbstate_t state;
for (inx = 0; inx < len; ++inx) {
wchar_t temp[2];
size_t check;
int result;
if (string[inx_wchars[inx]] == TAB) {
result = ((cache.list[inx] | 7) + 1) - cache.list[inx];
} else {
memset(&state, 0, sizeof(state));
memset(temp, 0, sizeof(temp));
check = mbrtowc(temp,
string + inx_wchars[inx],
num_bytes - (size_t) inx_wchars[inx],
&state);
if ((int) check <= 0) {
result = 1;
} else {
result = wcwidth(temp[0]);
}
if (result < 0) {
const wchar_t *printable;
cchar_t temp2, *temp2p = &temp2;
setcchar(temp2p, temp, 0, 0, 0);
printable = wunctrl(temp2p);
result = printable ? (int) wcslen(printable) : 1;
}
}
cache.list[inx + 1] = result;
if (inx != 0)
cache.list[inx + 1] += cache.list[inx];
}
} else
#endif /* USE_WIDE_CURSES */
{
for (inx = 0; inx < len; ++inx) {
chtype ch = UCH(string[inx]);
if (ch == TAB)
cache.list[inx + 1] =
((cache.list[inx] | 7) + 1) - cache.list[inx];
else if (isprint(ch))
cache.list[inx + 1] = 1;
else {
const char *printable;
printable = unctrl(ch);
cache.list[inx + 1] = (printable
? (int) strlen(printable)
: 1);
}
if (inx != 0)
cache.list[inx + 1] += cache.list[inx];
}
}
save_cache(&cache, string);
}
return cache.list;
}
/*
* Returns the number of columns used for a string. That happens to be the
* end-value of the cols[] array.
*/
int
dlg_count_columns(const char *string)
{
int result = 0;
int limit = dlg_count_wchars(string);
if (limit > 0) {
const int *cols = dlg_index_columns(string);
result = cols[limit];
} else {
result = (int) strlen(string);
}
return result;
}
/*
* Given a column limit, count the number of wide characters that can fit
* into that limit. The offset is used to skip over a leading character
* that was already written.
*/
int
dlg_limit_columns(const char *string, int limit, int offset)
{
const int *cols = dlg_index_columns(string);
int result = dlg_count_wchars(string);
while (result > 0 && (cols[result] - cols[offset]) > limit)
--result;
return result;
}
/*
* Updates the string and character-offset, given various editing characters
* or literal characters which are inserted at the character-offset.
*/
bool
dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
{
int i;
int len = (int) strlen(string);
int limit = dlg_count_wchars(string);
const int *indx = dlg_index_wchars(string);
int offset = dlg_find_index(indx, limit, *chr_offset);
int max_len = dlg_max_input(MAX_LEN);
bool edit = TRUE;
/* transform editing characters into equivalent function-keys */
if (!fkey) {
fkey = TRUE; /* assume we transform */
switch (key) {
case 0:
break;
case ESC:
case TAB:
fkey = FALSE; /* this is used for navigation */
break;
default:
fkey = FALSE; /* ...no, we did not transform */
break;
}
}
if (fkey) {
switch (key) {
case 0: /* special case for loop entry */
edit = force;
break;
case DLGK_GRID_LEFT:
if (*chr_offset)
*chr_offset = indx[offset - 1];
break;
case DLGK_GRID_RIGHT:
if (offset < limit)
*chr_offset = indx[offset + 1];
break;
case DLGK_BEGIN:
if (*chr_offset)
*chr_offset = 0;
break;
case DLGK_FINAL:
if (offset < limit)
*chr_offset = indx[limit];
break;
case DLGK_DELETE_LEFT:
if (offset) {
int gap = indx[offset] - indx[offset - 1];
*chr_offset = indx[offset - 1];
if (gap > 0) {
for (i = *chr_offset;
(string[i] = string[i + gap]) != '\0';
i++) {
;
}
}
}
break;
case DLGK_DELETE_RIGHT:
if (limit) {
if (--limit == 0) {
string[*chr_offset = 0] = '\0';
} else {
int gap = ((offset <= limit)
? (indx[offset + 1] - indx[offset])
: 0);
if (gap > 0) {
for (i = indx[offset];
(string[i] = string[i + gap]) != '\0';
i++) {
;
}
} else if (offset > 0) {
string[indx[offset - 1]] = '\0';
}
if (*chr_offset > indx[limit])
*chr_offset = indx[limit];
}
}
break;
case DLGK_DELETE_ALL:
string[*chr_offset = 0] = '\0';
break;
case DLGK_ENTER:
edit = 0;
break;
#ifdef KEY_RESIZE
case KEY_RESIZE:
edit = 0;
break;
#endif
case DLGK_GRID_UP:
case DLGK_GRID_DOWN:
case DLGK_FIELD_NEXT:
case DLGK_FIELD_PREV:
edit = 0;
break;
case ERR:
edit = 0;
break;
default:
beep();
break;
}
} else {
if (key == ESC || key == ERR) {
edit = 0;
} else {
if (len < max_len) {
for (i = ++len; i > *chr_offset; i--)
string[i] = string[i - 1];
string[*chr_offset] = (char) key;
*chr_offset += 1;
} else {
(void) beep();
}
}
}
return edit;
}
static void
compute_edit_offset(const char *string,
int chr_offset,
int x_last,
int *p_dpy_column,
int *p_scroll_amt)
{
const int *cols = dlg_index_columns(string);
const int *indx = dlg_index_wchars(string);
int limit = dlg_count_wchars(string);
int offset = dlg_find_index(indx, limit, chr_offset);
int offset2;
int dpy_column;
int n;
for (n = offset2 = 0; n <= offset; ++n) {
if ((cols[offset] - cols[n]) < x_last
&& (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
offset2 = n;
break;
}
}
dpy_column = cols[offset] - cols[offset2];
if (p_dpy_column != 0)
*p_dpy_column = dpy_column;
if (p_scroll_amt != 0)
*p_scroll_amt = offset2;
}
/*
* Given the character-offset in the string, returns the display-offset where
* we will position the cursor.
*/
int
dlg_edit_offset(char *string, int chr_offset, int x_last)
{
int result;
compute_edit_offset(string, chr_offset, x_last, &result, 0);
return result;
}
/*
* Displays the string, shifted as necessary, to fit within the box and show
* the current character-offset.
*/
void
dlg_show_string(WINDOW *win,
const char *string, /* string to display (may be multibyte) */
int chr_offset, /* character (not bytes) offset */
chtype attr, /* window-attributes */
int y_base, /* beginning row on screen */
int x_base, /* beginning column on screen */
int x_last, /* number of columns on screen */
bool hidden, /* if true, do not echo */
bool force) /* if true, force repaint */
{
x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
if (hidden && !dialog_vars.insecure) {
if (force) {
(void) wmove(win, y_base, x_base);
wrefresh(win);
}
} else {
const int *cols = dlg_index_columns(string);
const int *indx = dlg_index_wchars(string);
int limit = dlg_count_wchars(string);
int i, j, k;
int input_x;
int scrollamt;
compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
wattrset(win, attr);
(void) wmove(win, y_base, x_base);
for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
int check = cols[i + 1] - cols[scrollamt];
if (check <= x_last) {
for (j = indx[i]; j < indx[i + 1]; ++j) {
chtype ch = UCH(string[j]);
if (hidden && dialog_vars.insecure) {
waddch(win, '*');
} else if (ch == TAB) {
int count = cols[i + 1] - cols[i];
while (--count >= 0)
waddch(win, ' ');
} else {
waddch(win, ch);
}
}
k = check;
} else {
break;
}
}
while (k++ < x_last)
waddch(win, ' ');
(void) wmove(win, y_base, x_base + input_x);
wrefresh(win);
}
}
#ifdef NO_LEAKS
void
_dlg_inputstr_leaks(void)
{
#if USE_CACHING
while (cache_list != 0) {
CACHE *next = cache_list->next;
#ifdef HAVE_TSEARCH
tdelete(cache_list, &sorted_cache, compare_cache);
#endif
if (cache_list->string != 0)
free(cache_list->string);
if (cache_list->list != 0)
free(cache_list->list);
free(cache_list);
cache_list = next;
}
#endif /* USE_CACHING */
}
#endif /* NO_LEAKS */