]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/dialog/inputstr.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / dialog / inputstr.c
1 /*
2  *  $Id: inputstr.c,v 1.69 2011/01/16 21:52:35 tom Exp $
3  *
4  *  inputstr.c -- functions for input/display of a string
5  *
6  *  Copyright 2000-2010,2011    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     struct _cache *cache_at;    /* unique: associate caches by CACHE */
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 #ifdef HAVE_TSEARCH
69 static void *sorted_cache;
70 #endif
71
72 #ifdef USE_WIDE_CURSES
73 static int
74 have_locale(void)
75 {
76     static int result = -1;
77     if (result < 0) {
78         char *test = setlocale(LC_ALL, 0);
79         if (test == 0 || *test == 0) {
80             result = FALSE;
81         } else if (strcmp(test, "C") && strcmp(test, "POSIX")) {
82             result = TRUE;
83         } else {
84             result = FALSE;
85         }
86     }
87     return result;
88 }
89 #endif
90
91 #ifdef HAVE_TSEARCH
92 static int
93 compare_cache(const void *a, const void *b)
94 {
95     const CACHE *p = (const CACHE *) a;
96     const CACHE *q = (const CACHE *) b;
97     int result = 0;
98     result = (int) (p->cache_at - q->cache_at);
99     if (result == 0)
100         result = (int) (p->string_at - q->string_at);
101     return result;
102 }
103 #endif
104
105 static CACHE *
106 find_cache(CACHE * cache, const char *string)
107 {
108     CACHE *p;
109
110 #ifdef HAVE_TSEARCH
111     void *pp;
112     CACHE find;
113
114     memset(&find, 0, sizeof(find));
115     find.cache_at = cache;
116     find.string_at = string;
117
118     if ((pp = tfind(&find, &sorted_cache, compare_cache)) != 0) {
119         p = *(CACHE **) pp;
120     } else {
121         p = 0;
122     }
123 #else
124     for (p = cache_list; p != 0; p = p->next) {
125         if (p->cache_at == cache
126             && p->string_at == string) {
127             break;
128         }
129     }
130 #endif
131     return p;
132 }
133
134 static void
135 make_cache(CACHE * cache, const char *string)
136 {
137     CACHE *p;
138
139     p = dlg_calloc(CACHE, 1);
140     assert_ptr(p, "load_cache");
141     p->next = cache_list;
142     cache_list = p;
143
144     p->cache_at = cache;
145     p->string_at = string;
146
147     *cache = *p;
148 #ifdef HAVE_TSEARCH
149     (void) tsearch(p, &sorted_cache, compare_cache);
150 #endif
151 }
152
153 static void
154 load_cache(CACHE * cache, const char *string)
155 {
156     CACHE *p;
157
158     if ((p = find_cache(cache, string)) != 0) {
159         *cache = *p;
160     } else {
161         make_cache(cache, string);
162     }
163 }
164
165 static void
166 save_cache(CACHE * cache, const char *string)
167 {
168     CACHE *p;
169
170     if ((p = find_cache(cache, string)) != 0) {
171         CACHE *q = p->next;
172         *p = *cache;
173         p->next = q;
174     }
175 }
176 #else
177 #define SAME_CACHE(c,s,l) (c->string != 0)
178 #define load_cache(cache, string)       /* nothing */
179 #define save_cache(cache, string)       /* nothing */
180 #endif /* USE_WIDE_CURSES */
181
182 /*
183  * If the given string has not changed, we do not need to update the index.
184  * If we need to update the index, allocate enough memory for it.
185  */
186 static bool
187 same_cache2(CACHE * cache, const char *string, unsigned i_len)
188 {
189     unsigned need;
190     size_t s_len = strlen(string);
191
192     if (cache->s_len != 0
193         && cache->s_len >= s_len
194         && cache->list != 0
195         && SAME_CACHE(cache, string, (size_t) s_len)) {
196         return TRUE;
197     }
198
199     need = (i_len + 1);
200     if (cache->list == 0) {
201         cache->list = dlg_malloc(int, need);
202     } else if (cache->i_len < i_len) {
203         cache->list = dlg_realloc(int, need, cache->list);
204     }
205     cache->i_len = i_len;
206
207     if (cache->s_len >= s_len && cache->string != 0) {
208         strcpy(cache->string, string);
209     } else {
210         if (cache->string != 0)
211             free(cache->string);
212         cache->string = dlg_strclone(string);
213     }
214     cache->s_len = s_len;
215
216     return FALSE;
217 }
218
219 #ifdef USE_WIDE_CURSES
220 /*
221  * Like same_cache2(), but we are only concerned about caching a copy of the
222  * string and its associated length.
223  */
224 static bool
225 same_cache1(CACHE * cache, const char *string, size_t i_len)
226 {
227     size_t s_len = strlen(string);
228
229     if (cache->s_len == s_len
230         && SAME_CACHE(cache, string, (size_t) s_len)) {
231         return TRUE;
232     }
233
234     if (cache->s_len >= s_len && cache->string != 0) {
235         strcpy(cache->string, string);
236     } else {
237         if (cache->string != 0)
238             free(cache->string);
239         cache->string = dlg_strclone(string);
240     }
241     cache->s_len = s_len;
242     cache->i_len = i_len;
243
244     return FALSE;
245 }
246 #endif /* USE_CACHING */
247
248 /*
249  * Counts the number of bytes that make up complete wide-characters, up to byte
250  * 'len'.  If there is no locale set, simply return the original length.
251  */
252 #ifdef USE_WIDE_CURSES
253 static int
254 dlg_count_wcbytes(const char *string, size_t len)
255 {
256     int result;
257
258     if (have_locale()) {
259         static CACHE cache;
260
261         load_cache(&cache, string);
262         if (!same_cache1(&cache, string, len)) {
263             while (len != 0) {
264                 int part = 0;
265                 size_t code = 0;
266                 const char *src = cache.string;
267                 mbstate_t state;
268                 char save = cache.string[len];
269
270                 cache.string[len] = '\0';
271                 memset(&state, 0, sizeof(state));
272                 code = mbsrtowcs((wchar_t *) 0, &src, len, &state);
273                 cache.string[len] = save;
274                 if ((int) code >= 0) {
275                     break;
276                 }
277                 ++part;
278                 --len;
279             }
280             cache.i_len = len;
281             save_cache(&cache, string);
282         }
283         result = (int) cache.i_len;
284     } else {
285         result = (int) len;
286     }
287     return result;
288 }
289 #endif /* USE_WIDE_CURSES */
290
291 /*
292  * Counts the number of wide-characters in the string.
293  */
294 int
295 dlg_count_wchars(const char *string)
296 {
297     int result;
298
299 #ifdef USE_WIDE_CURSES
300     if (have_locale()) {
301         static CACHE cache;
302         size_t len = strlen(string);
303
304         load_cache(&cache, string);
305         if (!same_cache1(&cache, string, len)) {
306             const char *src = cache.string;
307             mbstate_t state;
308             int part = dlg_count_wcbytes(cache.string, len);
309             char save = cache.string[part];
310             size_t code;
311             wchar_t *temp = dlg_calloc(wchar_t, len + 1);
312
313             cache.string[part] = '\0';
314             memset(&state, 0, sizeof(state));
315             code = mbsrtowcs(temp, &src, (size_t) part, &state);
316             cache.i_len = ((int) code >= 0) ? wcslen(temp) : 0;
317             cache.string[part] = save;
318             free(temp);
319             save_cache(&cache, string);
320         }
321         result = (int) cache.i_len;
322     } else
323 #endif /* USE_WIDE_CURSES */
324     {
325         result = (int) strlen(string);
326     }
327     return result;
328 }
329
330 /*
331  * Build an index of the wide-characters in the string, so we can easily tell
332  * which byte-offset begins a given wide-character.
333  */
334 const int *
335 dlg_index_wchars(const char *string)
336 {
337     static CACHE cache;
338     unsigned len = (unsigned) dlg_count_wchars(string);
339     unsigned inx;
340
341     load_cache(&cache, string);
342     if (!same_cache2(&cache, string, len)) {
343         const char *current = string;
344
345         cache.list[0] = 0;
346         for (inx = 1; inx <= len; ++inx) {
347 #ifdef USE_WIDE_CURSES
348             if (have_locale()) {
349                 mbstate_t state;
350                 int width;
351                 memset(&state, 0, sizeof(state));
352                 width = (int) mbrlen(current, strlen(current), &state);
353                 if (width <= 0)
354                     width = 1;  /* FIXME: what if we have a control-char? */
355                 current += width;
356                 cache.list[inx] = cache.list[inx - 1] + width;
357             } else
358 #endif /* USE_WIDE_CURSES */
359             {
360                 (void) current;
361                 cache.list[inx] = (int) inx;
362             }
363         }
364         save_cache(&cache, string);
365     }
366     return cache.list;
367 }
368
369 /*
370  * Given the character-offset to find in the list, return the corresponding
371  * array index.
372  */
373 int
374 dlg_find_index(const int *list, int limit, int to_find)
375 {
376     int result;
377     for (result = 0; result <= limit; ++result) {
378         if (to_find == list[result]
379             || result == limit
380             || to_find < list[result + 1])
381             break;
382     }
383     return result;
384 }
385
386 /*
387  * Build a list of the display-columns for the given string's characters.
388  */
389 const int *
390 dlg_index_columns(const char *string)
391 {
392     static CACHE cache;
393     unsigned len = (unsigned) dlg_count_wchars(string);
394     unsigned inx;
395
396     load_cache(&cache, string);
397     if (!same_cache2(&cache, string, len)) {
398         cache.list[0] = 0;
399 #ifdef USE_WIDE_CURSES
400         if (have_locale()) {
401             size_t num_bytes = strlen(string);
402             const int *inx_wchars = dlg_index_wchars(string);
403             mbstate_t state;
404
405             for (inx = 0; inx < len; ++inx) {
406                 wchar_t temp[2];
407                 size_t check;
408                 int result;
409
410                 if (string[inx_wchars[inx]] == TAB) {
411                     result = ((cache.list[inx] | 7) + 1) - cache.list[inx];
412                 } else {
413                     memset(&state, 0, sizeof(state));
414                     memset(temp, 0, sizeof(temp));
415                     check = mbrtowc(temp,
416                                     string + inx_wchars[inx],
417                                     num_bytes - (size_t) inx_wchars[inx],
418                                     &state);
419                     if ((int) check <= 0) {
420                         result = 1;
421                     } else {
422                         result = wcwidth(temp[0]);
423                     }
424                     if (result < 0) {
425                         const wchar_t *printable;
426                         cchar_t temp2, *temp2p = &temp2;
427                         setcchar(temp2p, temp, 0, 0, 0);
428                         printable = wunctrl(temp2p);
429                         result = printable ? (int) wcslen(printable) : 1;
430                     }
431                 }
432                 cache.list[inx + 1] = result;
433                 if (inx != 0)
434                     cache.list[inx + 1] += cache.list[inx];
435             }
436         } else
437 #endif /* USE_WIDE_CURSES */
438         {
439             for (inx = 0; inx < len; ++inx) {
440                 chtype ch = UCH(string[inx]);
441
442                 if (ch == TAB)
443                     cache.list[inx + 1] =
444                         ((cache.list[inx] | 7) + 1) - cache.list[inx];
445                 else if (isprint(ch))
446                     cache.list[inx + 1] = 1;
447                 else {
448                     const char *printable;
449                     printable = unctrl(ch);
450                     cache.list[inx + 1] = (printable
451                                            ? (int) strlen(printable)
452                                            : 1);
453                 }
454                 if (inx != 0)
455                     cache.list[inx + 1] += cache.list[inx];
456             }
457         }
458         save_cache(&cache, string);
459     }
460     return cache.list;
461 }
462
463 /*
464  * Returns the number of columns used for a string.  That happens to be the
465  * end-value of the cols[] array.
466  */
467 int
468 dlg_count_columns(const char *string)
469 {
470     int result = 0;
471     int limit = dlg_count_wchars(string);
472     if (limit > 0) {
473         const int *cols = dlg_index_columns(string);
474         result = cols[limit];
475     } else {
476         result = (int) strlen(string);
477     }
478     return result;
479 }
480
481 /*
482  * Given a column limit, count the number of wide characters that can fit
483  * into that limit.  The offset is used to skip over a leading character
484  * that was already written.
485  */
486 int
487 dlg_limit_columns(const char *string, int limit, int offset)
488 {
489     const int *cols = dlg_index_columns(string);
490     int result = dlg_count_wchars(string);
491
492     while (result > 0 && (cols[result] - cols[offset]) > limit)
493         --result;
494     return result;
495 }
496
497 /*
498  * Updates the string and character-offset, given various editing characters
499  * or literal characters which are inserted at the character-offset.
500  */
501 bool
502 dlg_edit_string(char *string, int *chr_offset, int key, int fkey, bool force)
503 {
504     int i;
505     int len = (int) strlen(string);
506     int limit = dlg_count_wchars(string);
507     const int *indx = dlg_index_wchars(string);
508     int offset = dlg_find_index(indx, limit, *chr_offset);
509     int max_len = dlg_max_input(MAX_LEN);
510     bool edit = TRUE;
511
512     /* transform editing characters into equivalent function-keys */
513     if (!fkey) {
514         fkey = TRUE;            /* assume we transform */
515         switch (key) {
516         case 0:
517             break;
518         case ESC:
519         case TAB:
520             fkey = FALSE;       /* this is used for navigation */
521             break;
522         default:
523             fkey = FALSE;       /* ...no, we did not transform */
524             break;
525         }
526     }
527
528     if (fkey) {
529         switch (key) {
530         case 0:         /* special case for loop entry */
531             edit = force;
532             break;
533         case DLGK_GRID_LEFT:
534             if (*chr_offset)
535                 *chr_offset = indx[offset - 1];
536             break;
537         case DLGK_GRID_RIGHT:
538             if (offset < limit)
539                 *chr_offset = indx[offset + 1];
540             break;
541         case DLGK_BEGIN:
542             if (*chr_offset)
543                 *chr_offset = 0;
544             break;
545         case DLGK_FINAL:
546             if (offset < limit)
547                 *chr_offset = indx[limit];
548             break;
549         case DLGK_DELETE_LEFT:
550             if (offset) {
551                 int gap = indx[offset] - indx[offset - 1];
552                 *chr_offset = indx[offset - 1];
553                 if (gap > 0) {
554                     for (i = *chr_offset;
555                          (string[i] = string[i + gap]) != '\0';
556                          i++) {
557                         ;
558                     }
559                 }
560             }
561             break;
562         case DLGK_DELETE_RIGHT:
563             if (limit) {
564                 if (--limit == 0) {
565                     string[*chr_offset = 0] = '\0';
566                 } else {
567                     int gap = ((offset <= limit)
568                                ? (indx[offset + 1] - indx[offset])
569                                : 0);
570                     if (gap > 0) {
571                         for (i = indx[offset];
572                              (string[i] = string[i + gap]) != '\0';
573                              i++) {
574                             ;
575                         }
576                     } else if (offset > 0) {
577                         string[indx[offset - 1]] = '\0';
578                     }
579                     if (*chr_offset > indx[limit])
580                         *chr_offset = indx[limit];
581                 }
582             }
583             break;
584         case DLGK_DELETE_ALL:
585             string[*chr_offset = 0] = '\0';
586             break;
587         case DLGK_ENTER:
588             edit = 0;
589             break;
590 #ifdef KEY_RESIZE
591         case KEY_RESIZE:
592             edit = 0;
593             break;
594 #endif
595         case DLGK_GRID_UP:
596         case DLGK_GRID_DOWN:
597         case DLGK_FIELD_NEXT:
598         case DLGK_FIELD_PREV:
599             edit = 0;
600             break;
601         case ERR:
602             edit = 0;
603             break;
604         default:
605             beep();
606             break;
607         }
608     } else {
609         if (key == ESC || key == ERR) {
610             edit = 0;
611         } else {
612             if (len < max_len) {
613                 for (i = ++len; i > *chr_offset; i--)
614                     string[i] = string[i - 1];
615                 string[*chr_offset] = (char) key;
616                 *chr_offset += 1;
617             } else {
618                 (void) beep();
619             }
620         }
621     }
622     return edit;
623 }
624
625 static void
626 compute_edit_offset(const char *string,
627                     int chr_offset,
628                     int x_last,
629                     int *p_dpy_column,
630                     int *p_scroll_amt)
631 {
632     const int *cols = dlg_index_columns(string);
633     const int *indx = dlg_index_wchars(string);
634     int limit = dlg_count_wchars(string);
635     int offset = dlg_find_index(indx, limit, chr_offset);
636     int offset2;
637     int dpy_column;
638     int n;
639
640     for (n = offset2 = 0; n <= offset; ++n) {
641         if ((cols[offset] - cols[n]) < x_last
642             && (offset == limit || (cols[offset + 1] - cols[n]) < x_last)) {
643             offset2 = n;
644             break;
645         }
646     }
647
648     dpy_column = cols[offset] - cols[offset2];
649
650     if (p_dpy_column != 0)
651         *p_dpy_column = dpy_column;
652     if (p_scroll_amt != 0)
653         *p_scroll_amt = offset2;
654 }
655
656 /*
657  * Given the character-offset in the string, returns the display-offset where
658  * we will position the cursor.
659  */
660 int
661 dlg_edit_offset(char *string, int chr_offset, int x_last)
662 {
663     int result;
664
665     compute_edit_offset(string, chr_offset, x_last, &result, 0);
666
667     return result;
668 }
669
670 /*
671  * Displays the string, shifted as necessary, to fit within the box and show
672  * the current character-offset.
673  */
674 void
675 dlg_show_string(WINDOW *win,
676                 const char *string,     /* string to display (may be multibyte) */
677                 int chr_offset, /* character (not bytes) offset */
678                 chtype attr,    /* window-attributes */
679                 int y_base,     /* beginning row on screen */
680                 int x_base,     /* beginning column on screen */
681                 int x_last,     /* number of columns on screen */
682                 bool hidden,    /* if true, do not echo */
683                 bool force)     /* if true, force repaint */
684 {
685     x_last = MIN(x_last + x_base, getmaxx(win)) - x_base;
686
687     if (hidden && !dialog_vars.insecure) {
688         if (force) {
689             (void) wmove(win, y_base, x_base);
690             wrefresh(win);
691         }
692     } else {
693         const int *cols = dlg_index_columns(string);
694         const int *indx = dlg_index_wchars(string);
695         int limit = dlg_count_wchars(string);
696
697         int i, j, k;
698         int input_x;
699         int scrollamt;
700
701         compute_edit_offset(string, chr_offset, x_last, &input_x, &scrollamt);
702
703         wattrset(win, attr);
704         (void) wmove(win, y_base, x_base);
705         for (i = scrollamt, k = 0; i < limit && k < x_last; ++i) {
706             int check = cols[i + 1] - cols[scrollamt];
707             if (check <= x_last) {
708                 for (j = indx[i]; j < indx[i + 1]; ++j) {
709                     chtype ch = UCH(string[j]);
710                     if (hidden && dialog_vars.insecure) {
711                         waddch(win, '*');
712                     } else if (ch == TAB) {
713                         int count = cols[i + 1] - cols[i];
714                         while (--count >= 0)
715                             waddch(win, ' ');
716                     } else {
717                         waddch(win, ch);
718                     }
719                 }
720                 k = check;
721             } else {
722                 break;
723             }
724         }
725         while (k++ < x_last)
726             waddch(win, ' ');
727         (void) wmove(win, y_base, x_base + input_x);
728         wrefresh(win);
729     }
730 }
731
732 #ifdef NO_LEAKS
733 void
734 _dlg_inputstr_leaks(void)
735 {
736 #if USE_CACHING
737     while (cache_list != 0) {
738         CACHE *next = cache_list->next;
739 #ifdef HAVE_TSEARCH
740         tdelete(cache_list, &sorted_cache, compare_cache);
741 #endif
742         if (cache_list->string != 0)
743             free(cache_list->string);
744         if (cache_list->list != 0)
745             free(cache_list->list);
746         free(cache_list);
747         cache_list = next;
748     }
749 #endif /* USE_CACHING */
750 }
751 #endif /* NO_LEAKS */