2 * $Id: inputstr.c,v 1.84 2014/09/01 16:11:08 tom Exp $
4 * inputstr.c -- functions for input/display of a string
6 * Copyright 2000-2013,2014 Thomas E. Dickey
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.
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.
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.
33 #if defined(HAVE_SEARCH_H) && defined(HAVE_TSEARCH)
43 #if defined(USE_WIDE_CURSES)
45 #elif defined(HAVE_XDIALOG)
46 #define USE_CACHING 1 /* editbox really needs caching! */
51 typedef struct _cache {
54 int cache_num; /* tells what type of data is in list[] */
55 const char *string_at; /* unique: associate caches by char* */
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 */
64 #define SAME_CACHE(c,s,l) (c->string != 0 && memcmp(c->string,s,l) == 0)
66 static CACHE *cache_list;
77 static void *sorted_cache;
80 #ifdef USE_WIDE_CURSES
84 static int result = -1;
86 char *test = setlocale(LC_ALL, 0);
87 if (test == 0 || *test == 0) {
89 } else if (strcmp(test, "C") && strcmp(test, "POSIX")) {
103 show_tsearch(const void *nodep, const VISIT which, const int depth)
105 const CACHE *p = *(CACHE * const *) nodep;
107 if (which == postorder || which == leaf) {
108 dlg_trace_msg("\tcache %p %p:%s\n", p, p->string, p->string);
113 trace_cache(const char *fn, int ln)
115 dlg_trace_msg("trace_cache %s@%d\n", fn, ln);
116 twalk(sorted_cache, show_tsearch);
120 #define trace_cache(fn, ln) /* nothing */
123 #define CMP(a,b) (((a) > (b)) ? 1 : (((a) < (b)) ? -1 : 0))
126 compare_cache(const void *a, const void *b)
128 const CACHE *p = (const CACHE *) a;
129 const CACHE *q = (const CACHE *) b;
130 int result = CMP(p->cache_num, q->cache_num);
132 result = CMP(p->string_at, q->string_at);
138 find_cache(int cache_num, const char *string)
146 memset(&find, 0, sizeof(find));
147 find.cache_num = cache_num;
148 find.string_at = string;
150 if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) {
156 for (p = cache_list; p != 0; p = p->next) {
157 if (p->string_at == string) {
166 make_cache(int cache_num, const char *string)
170 p = dlg_calloc(CACHE, 1);
171 assert_ptr(p, "load_cache");
172 p->next = cache_list;
175 p->cache_num = cache_num;
176 p->string_at = string;
179 (void) tsearch(p, &sorted_cache, compare_cache);
185 load_cache(int cache_num, const char *string)
189 if ((p = find_cache(cache_num, string)) == 0) {
190 p = make_cache(cache_num, string);
195 static CACHE my_cache;
196 #define SAME_CACHE(c,s,l) (c->string != 0)
197 #define load_cache(cache, string) &my_cache
198 #endif /* USE_CACHING */
201 * If the given string has not changed, we do not need to update the index.
202 * If we need to update the index, allocate enough memory for it.
205 same_cache2(CACHE * cache, const char *string, unsigned i_len)
208 size_t s_len = strlen(string);
211 if (cache->s_len == 0
212 || cache->s_len < s_len
214 || !SAME_CACHE(cache, string, (size_t) s_len)) {
217 if (cache->list == 0) {
218 cache->list = dlg_malloc(int, need);
219 } else if (cache->i_len < i_len) {
220 cache->list = dlg_realloc(int, need, cache->list);
222 assert_ptr(cache->list, "load_cache");
223 cache->i_len = i_len;
225 if (cache->s_len >= s_len && cache->string != 0) {
226 strcpy(cache->string, string);
228 if (cache->string != 0)
230 cache->string = dlg_strclone(string);
232 cache->s_len = s_len;
239 #ifdef USE_WIDE_CURSES
241 * Like same_cache2(), but we are only concerned about caching a copy of the
242 * string and its associated length.
245 same_cache1(CACHE * cache, const char *string, size_t i_len)
247 size_t s_len = strlen(string);
250 if (cache->s_len != s_len
251 || !SAME_CACHE(cache, string, (size_t) s_len)) {
253 if (cache->s_len >= s_len && cache->string != 0) {
254 strcpy(cache->string, string);
256 if (cache->string != 0)
258 cache->string = dlg_strclone(string);
260 cache->s_len = s_len;
261 cache->i_len = i_len;
267 #endif /* USE_CACHING */
270 * Counts the number of bytes that make up complete wide-characters, up to byte
271 * 'len'. If there is no locale set, simply return the original length.
273 #ifdef USE_WIDE_CURSES
275 dlg_count_wcbytes(const char *string, size_t len)
280 CACHE *cache = load_cache(cCntWideBytes, string);
281 if (!same_cache1(cache, string, len)) {
284 const char *src = cache->string;
286 char save = cache->string[len];
288 cache->string[len] = '\0';
289 memset(&state, 0, sizeof(state));
290 code = mbsrtowcs((wchar_t *) 0, &src, len, &state);
291 cache->string[len] = save;
292 if ((int) code >= 0) {
299 result = (int) cache->i_len;
305 #endif /* USE_WIDE_CURSES */
308 * Counts the number of wide-characters in the string.
311 dlg_count_wchars(const char *string)
314 #ifdef USE_WIDE_CURSES
317 size_t len = strlen(string);
318 CACHE *cache = load_cache(cCntWideChars, string);
320 if (!same_cache1(cache, string, len)) {
321 const char *src = cache->string;
323 int part = dlg_count_wcbytes(cache->string, len);
324 char save = cache->string[part];
326 wchar_t *temp = dlg_calloc(wchar_t, len + 1);
329 cache->string[part] = '\0';
330 memset(&state, 0, sizeof(state));
331 code = mbsrtowcs(temp, &src, (size_t) part, &state);
332 cache->i_len = ((int) code >= 0) ? wcslen(temp) : 0;
333 cache->string[part] = save;
339 result = (int) cache->i_len;
341 #endif /* USE_WIDE_CURSES */
343 result = (int) strlen(string);
349 * Build an index of the wide-characters in the string, so we can easily tell
350 * which byte-offset begins a given wide-character.
353 dlg_index_wchars(const char *string)
355 unsigned len = (unsigned) dlg_count_wchars(string);
357 CACHE *cache = load_cache(cInxWideChars, string);
359 if (!same_cache2(cache, string, len)) {
360 const char *current = string;
363 for (inx = 1; inx <= len; ++inx) {
364 #ifdef USE_WIDE_CURSES
368 memset(&state, 0, sizeof(state));
369 width = (int) mbrlen(current, strlen(current), &state);
371 width = 1; /* FIXME: what if we have a control-char? */
373 cache->list[inx] = cache->list[inx - 1] + width;
375 #endif /* USE_WIDE_CURSES */
378 cache->list[inx] = (int) inx;
386 * Given the character-offset to find in the list, return the corresponding
390 dlg_find_index(const int *list, int limit, int to_find)
393 for (result = 0; result <= limit; ++result) {
394 if (to_find == list[result]
396 || ((result < limit) && (to_find < list[result + 1]))) {
404 * Build a list of the display-columns for the given string's characters.
407 dlg_index_columns(const char *string)
409 unsigned len = (unsigned) dlg_count_wchars(string);
411 CACHE *cache = load_cache(cInxCols, string);
413 if (!same_cache2(cache, string, len)) {
415 #ifdef USE_WIDE_CURSES
417 size_t num_bytes = strlen(string);
418 const int *inx_wchars = dlg_index_wchars(string);
421 for (inx = 0; inx < len; ++inx) {
426 if (string[inx_wchars[inx]] == TAB) {
427 result = ((cache->list[inx] | 7) + 1) - cache->list[inx];
429 memset(&state, 0, sizeof(state));
430 memset(temp, 0, sizeof(temp));
431 check = mbrtowc(temp,
432 string + inx_wchars[inx],
433 num_bytes - (size_t) inx_wchars[inx],
435 if ((int) check <= 0) {
438 result = wcwidth(temp[0]);
441 const wchar_t *printable;
442 cchar_t temp2, *temp2p = &temp2;
443 setcchar(temp2p, temp, 0, 0, 0);
444 printable = wunctrl(temp2p);
445 result = printable ? (int) wcslen(printable) : 1;
448 cache->list[inx + 1] = result;
450 cache->list[inx + 1] += cache->list[inx];
453 #endif /* USE_WIDE_CURSES */
455 for (inx = 0; inx < len; ++inx) {
456 chtype ch = UCH(string[inx]);
459 cache->list[inx + 1] =
460 ((cache->list[inx] | 7) + 1) - cache->list[inx];
461 else if (isprint(ch))
462 cache->list[inx + 1] = 1;
464 const char *printable;
465 printable = unctrl(ch);
466 cache->list[inx + 1] = (printable
467 ? (int) strlen(printable)
471 cache->list[inx + 1] += cache->list[inx];
479 * Returns the number of columns used for a string. That happens to be the
480 * end-value of the cols[] array.
483 dlg_count_columns(const char *string)
486 int limit = dlg_count_wchars(string);
488 const int *cols = dlg_index_columns(string);
489 result = cols[limit];
491 result = (int) strlen(string);
493 dlg_finish_string(string);
498 * Given a column limit, count the number of wide characters that can fit
499 * into that limit. The offset is used to skip over a leading character
500 * that was already written.
503 dlg_limit_columns(const char *string, int limit, int offset)
505 const int *cols = dlg_index_columns(string);
506 int result = dlg_count_wchars(string);
508 while (result > 0 && (cols[result] - cols[offset]) > limit)
514 * Updates the string and character-offset, given various editing characters
515 * or literal characters which are inserted at the character-offset.
518 dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
521 int len = (int) strlen(string);
522 int limit = dlg_count_wchars(string);
523 const int *indx = dlg_index_wchars(string);
524 int offset = dlg_find_index(indx, limit, *chr_offset);
525 int max_len = dlg_max_input(MAX_LEN);
528 /* transform editing characters into equivalent function-keys */
530 fkey = TRUE; /* assume we transform */
536 fkey = FALSE; /* this is used for navigation */
539 fkey = FALSE; /* ...no, we did not transform */
546 case 0: /* special case for loop entry */
550 if (*chr_offset && offset > 0)
551 *chr_offset = indx[offset - 1];
553 case DLGK_GRID_RIGHT:
555 *chr_offset = indx[offset + 1];
563 *chr_offset = indx[limit];
565 case DLGK_DELETE_LEFT:
567 int gap = indx[offset] - indx[offset - 1];
568 *chr_offset = indx[offset - 1];
570 for (i = *chr_offset;
571 (string[i] = string[i + gap]) != '\0';
578 case DLGK_DELETE_RIGHT:
581 string[*chr_offset = 0] = '\0';
583 int gap = ((offset <= limit)
584 ? (indx[offset + 1] - indx[offset])
587 for (i = indx[offset];
588 (string[i] = string[i + gap]) != '\0';
592 } else if (offset > 0) {
593 string[indx[offset - 1]] = '\0';
595 if (*chr_offset > indx[limit])
596 *chr_offset = indx[limit];
600 case DLGK_DELETE_ALL:
601 string[*chr_offset = 0] = '\0';
613 case DLGK_FIELD_NEXT:
614 case DLGK_FIELD_PREV:
625 if (key == ESC || key == ERR) {
629 for (i = ++len; i > *chr_offset; i--)
630 string[i] = string[i - 1];
631 string[*chr_offset] = (char) key;
642 compute_edit_offset(const char *string,
648 const int *cols = dlg_index_columns(string);
649 const int *indx = dlg_index_wchars(string);
650 int limit = dlg_count_wchars(string);
651 int offset = dlg_find_index(indx, limit, chr_offset);
656 for (n = offset2 = 0; n <= offset; ++n) {
657 if ((cols[offset] - cols[n]) < x_last
658 && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
664 dpy_column = cols[offset] - cols[offset2];
666 if (p_dpy_column != 0)
667 *p_dpy_column = dpy_column;
668 if (p_scroll_amt != 0)
669 *p_scroll_amt = offset2;
673 * Given the character-offset in the string, returns the display-offset where
674 * we will position the cursor.
677 dlg_edit_offset(char *string, int chr_offset, int x_last)
681 compute_edit_offset(string, chr_offset, x_last, &result, 0);
687 * Displays the string, shifted as necessary, to fit within the box and show
688 * the current character-offset.
691 dlg_show_string(WINDOW *win,
692 const char *string, /* string to display (may be multibyte) */
693 int chr_offset, /* character (not bytes) offset */
694 chtype attr, /* window-attributes */
695 int y_base, /* beginning row on screen */
696 int x_base, /* beginning column on screen */
697 int x_last, /* number of columns on screen */
698 bool hidden, /* if true, do not echo */
699 bool force) /* if true, force repaint */
701 x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
703 if (hidden && !dialog_vars.insecure) {
705 (void) wmove(win, y_base, x_base);
709 const int *cols = dlg_index_columns(string);
710 const int *indx = dlg_index_wchars(string);
711 int limit = dlg_count_wchars(string);
717 compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
719 (void) wattrset(win, attr);
720 (void) wmove(win, y_base, x_base);
721 for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
722 int check = cols[i + 1] - cols[scrollamt];
723 if (check <= x_last) {
724 for (j = indx[i]; j < indx[i + 1]; ++j) {
725 chtype ch = UCH(string[j]);
726 if (hidden && dialog_vars.insecure) {
728 } else if (ch == TAB) {
729 int count = cols[i + 1] - cols[i];
743 (void) wmove(win, y_base, x_base + input_x);
749 * Discard cached data for the given string.
752 dlg_finish_string(const char *string)
755 if ((string != 0) && dialog_state.finish_string) {
756 CACHE *p = cache_list;
761 if (p->string_at == string) {
763 if (tdelete(p, &sorted_cache, compare_cache) == 0) {
766 trace_cache(__FILE__, __LINE__);
772 if (p == cache_list) {
773 cache_list = p->next;
794 _dlg_inputstr_leaks(void)
797 dialog_state.finish_string = TRUE;
798 trace_cache(__FILE__, __LINE__);
799 while (cache_list != 0) {
800 dlg_finish_string(cache_list->string_at);
802 #endif /* USE_CACHING */
804 #endif /* NO_LEAKS */