/* * $Id: inputstr.c,v 1.72 2012/12/30 22:11:37 tom Exp $ * * inputstr.c -- functions for input/display of a string * * Copyright 2000-2011,2012 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 #include #include #ifdef HAVE_SETLOCALE #include #endif #if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH) #include #else #undef HAVE_TSEARCH #endif #ifdef NEED_WCHAR_H #include #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) { 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; } --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); if (temp != 0) { 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); } else { cache.i_len = 0; } } 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 || ((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 && offset > 0) *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); (void) 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 */