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 */