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
51 typedef 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
66 static CACHE *cache_list;
67
68 typedef enum {
69 cInxCols
70 ,cCntWideBytes
71 ,cCntWideChars
72 ,cInxWideChars
73 ,cMAX
74 } CACHE_USED;
75
76 #ifdef HAVE_TSEARCH
77 static void *sorted_cache;
78 #endif
79
80 #ifdef USE_WIDE_CURSES
81 static int
have_locale(void)82 have_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
102 static void
103 show_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
112 static void
113 trace_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
123 static int
compare_cache(const void * a,const void * b)124 compare_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
135 static CACHE *
find_cache(int cache_num,const char * string)136 find_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
163 static CACHE *
make_cache(int cache_num,const char * string)164 make_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
182 static CACHE *
load_cache(int cache_num,const char * string)183 load_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
193 static 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 */
202 static bool
same_cache2(CACHE * cache,const char * string,unsigned i_len)203 same_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 */
242 static bool
same_cache1(CACHE * cache,const char * string,size_t i_len)243 same_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
272 static int
dlg_count_wcbytes(const char * string,size_t len)273 dlg_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 */
308 int
dlg_count_wchars(const char * string)309 dlg_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 */
350 const int *
dlg_index_wchars(const char * string)351 dlg_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 */
387 int
dlg_find_index(const int * list,int limit,int to_find)388 dlg_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 */
404 const int *
dlg_index_columns(const char * string)405 dlg_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 */
480 int
dlg_count_columns(const char * string)481 dlg_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 */
500 int
dlg_limit_columns(const char * string,int limit,int offset)501 dlg_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 */
515 bool
dlg_edit_string(char * string,int * chr_offset,int key,int fkey,bool force)516 dlg_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
639 static void
compute_edit_offset(const char * string,int chr_offset,int x_last,int * p_dpy_column,int * p_scroll_amt)640 compute_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 */
674 int
dlg_edit_offset(char * string,int chr_offset,int x_last)675 dlg_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 */
688 void
dlg_show_string(WINDOW * win,const char * string,int chr_offset,chtype attr,int y_base,int x_base,int x_last,bool hidden,bool force)689 dlg_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 */
749 void
dlg_finish_string(const char * string)750 dlg_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
791 void
_dlg_inputstr_leaks(void)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