1 /*
2 * (c) 2004 by Kalle Wallin <kaw@linux.se>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 */
19 #include "wreadln.h"
20 #include "charset.h"
21 #include "screen_utils.h"
22 #include "config.h"
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
29 #ifdef ENABLE_WIDE
30 #include <sys/poll.h>
31 #endif
33 #define KEY_CTRL_A 1
34 #define KEY_CTRL_B 2
35 #define KEY_CTRL_C 3
36 #define KEY_CTRL_D 4
37 #define KEY_CTRL_E 5
38 #define KEY_CTRL_F 6
39 #define KEY_CTRL_G 7
40 #define KEY_CTRL_K 11
41 #define KEY_CTRL_N 14
42 #define KEY_CTRL_P 16
43 #define KEY_CTRL_U 21
44 #define KEY_CTRL_Z 26
45 #define KEY_BCKSPC 8
46 #define TAB 9
48 struct wreadln {
49 /** the ncurses window where this field is displayed */
50 WINDOW *const w;
52 /** the origin coordinates in the window */
53 unsigned x, y;
55 /** the screen width of the input field */
56 unsigned width;
58 /** is the input masked, i.e. characters displayed as '*'? */
59 const gboolean masked;
61 /** the byte position of the cursor */
62 size_t cursor;
64 /** the byte position displayed at the origin (for horizontal
65 scrolling) */
66 size_t start;
68 /** the current value */
69 gchar line[1024];
70 };
72 /** max items stored in the history list */
73 static const guint wrln_max_history_length = 32;
75 void *wrln_completion_callback_data = NULL;
76 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
77 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
79 /** converts a byte position to a screen column */
80 static unsigned
81 byte_to_screen(const gchar *data, size_t x)
82 {
83 #ifdef ENABLE_WIDE
84 gchar *dup;
85 char *p;
86 unsigned width;
88 assert(x <= strlen(data));
90 dup = g_strdup(data);
91 dup[x] = 0;
92 p = locale_to_utf8(dup);
93 g_free(dup);
95 width = utf8_width(p);
96 g_free(p);
98 return width;
99 #else
100 (void)data;
102 return (unsigned)x;
103 #endif
104 }
106 /** finds the first character which doesn't fit on the screen */
107 static size_t
108 screen_to_bytes(const gchar *data, unsigned width)
109 {
110 #ifdef ENABLE_WIDE
111 size_t length = strlen(data);
112 gchar *dup = g_strdup(data);
113 char *p;
114 unsigned p_width;
116 while (true) {
117 dup[length] = 0;
118 p = locale_to_utf8(dup);
119 p_width = utf8_width(p);
120 g_free(p);
121 if (p_width <= width)
122 break;
124 --length;
125 }
127 g_free(dup);
129 return length;
130 #else
131 (void)data;
133 return (size_t)width;
134 #endif
135 }
137 /** returns the screen colum where the cursor is located */
138 static unsigned
139 cursor_column(const struct wreadln *wr)
140 {
141 return byte_to_screen(wr->line + wr->start,
142 wr->cursor - wr->start);
143 }
145 /** returns the offset in the string to align it at the right border
146 of the screen */
147 static inline size_t
148 right_align_bytes(const gchar *data, size_t right, unsigned width)
149 {
150 #ifdef ENABLE_WIDE
151 gchar *dup;
152 size_t start = 0;
154 assert(right <= strlen(data));
156 dup = g_strdup(data);
157 dup[right] = 0;
159 while (dup[start] != 0) {
160 char *p = locale_to_utf8(dup + start), *q;
161 unsigned p_width = utf8_width(p);
162 gunichar c;
164 if (p_width < width) {
165 g_free(p);
166 break;
167 }
169 c = g_utf8_get_char(p);
170 p[g_unichar_to_utf8(c, NULL)] = 0;
171 q = utf8_to_locale(p);
172 g_free(p);
174 start += strlen(q);
175 g_free(q);
176 }
178 g_free(dup);
180 return start;
181 #else
182 (void)data;
184 return right >= width ? right + 1 - width : 0;
185 #endif
186 }
188 /** returns the size (in bytes) of the next character */
189 static inline size_t
190 next_char_size(const gchar *data)
191 {
192 #ifdef ENABLE_WIDE
193 char *p = locale_to_utf8(data), *q;
194 gunichar c;
195 size_t size;
197 c = g_utf8_get_char(p);
198 p[g_unichar_to_utf8(c, NULL)] = 0;
199 q = utf8_to_locale(p);
200 g_free(p);
202 size = strlen(q);
203 g_free(q);
205 return size;
206 #else
207 (void)data;
209 return 1;
210 #endif
211 }
213 /** returns the size (in bytes) of the previous character */
214 static inline size_t
215 prev_char_size(const gchar *data, size_t x)
216 {
217 #ifdef ENABLE_WIDE
218 char *p = locale_to_utf8(data), *q;
219 gunichar c;
220 size_t size;
222 assert(x > 0);
224 q = p;
225 while (true) {
226 c = g_utf8_get_char(q);
227 size = g_unichar_to_utf8(c, NULL);
228 if (size > x)
229 size = x;
230 x -= size;
231 if (x == 0) {
232 g_free(p);
233 return size;
234 }
236 q += size;
237 }
238 #else
239 (void)data;
240 (void)x;
242 return 1;
243 #endif
244 }
246 /* move the cursor one step to the right */
247 static inline void cursor_move_right(struct wreadln *wr)
248 {
249 size_t size;
251 if (wr->line[wr->cursor] == 0)
252 return;
254 size = next_char_size(wr->line + wr->cursor);
255 wr->cursor += size;
256 if (cursor_column(wr) >= wr->width)
257 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
258 }
260 /* move the cursor one step to the left */
261 static inline void cursor_move_left(struct wreadln *wr)
262 {
263 size_t size;
265 if (wr->cursor == 0)
266 return;
268 size = prev_char_size(wr->line, wr->cursor);
269 assert(wr->cursor >= size);
270 wr->cursor -= size;
271 if (wr->cursor < wr->start)
272 wr->start = wr->cursor;
273 }
275 /* move the cursor to the end of the line */
276 static inline void cursor_move_to_eol(struct wreadln *wr)
277 {
278 wr->cursor = strlen(wr->line);
279 if (cursor_column(wr) >= wr->width)
280 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
281 }
283 /* draw line buffer and update cursor position */
284 static inline void drawline(const struct wreadln *wr)
285 {
286 wmove(wr->w, wr->y, wr->x);
287 /* clear input area */
288 whline(wr->w, ' ', wr->width);
289 /* print visible part of the line buffer */
290 if (wr->masked)
291 whline(wr->w, '*', utf8_width(wr->line + wr->start));
292 else
293 waddnstr(wr->w, wr->line + wr->start,
294 screen_to_bytes(wr->line, wr->width));
295 /* move the cursor to the correct position */
296 wmove(wr->w, wr->y, wr->x + cursor_column(wr));
297 /* tell ncurses to redraw the screen */
298 doupdate();
299 }
301 #ifdef ENABLE_WIDE
302 static bool
303 multibyte_is_complete(const char *p, size_t length)
304 {
305 GError *error = NULL;
306 gchar *q = g_locale_to_utf8(p, length,
307 NULL, NULL, &error);
308 if (q != NULL) {
309 g_free(q);
310 return true;
311 } else {
312 g_error_free(error);
313 return false;
314 }
315 }
316 #endif
318 static void
319 wreadln_insert_byte(struct wreadln *wr, gint key)
320 {
321 size_t rest = strlen(wr->line + wr->cursor) + 1;
322 #ifdef ENABLE_WIDE
323 char buffer[32] = { key };
324 size_t length = 1;
325 struct pollfd pfd = {
326 .fd = 0,
327 .events = POLLIN,
328 };
329 int ret;
331 /* wide version: try to complete the multibyte sequence */
333 while (length < sizeof(buffer)) {
334 if (multibyte_is_complete(buffer, length))
335 /* sequence is complete */
336 break;
338 /* poll for more bytes on stdin, without timeout */
340 ret = poll(&pfd, 1, 0);
341 if (ret <= 0)
342 /* no more input from keyboard */
343 break;
345 buffer[length++] = wgetch(wr->w);
346 }
348 memmove(wr->line + wr->cursor + length,
349 wr->line + wr->cursor, rest);
350 memcpy(wr->line + wr->cursor, buffer, length);
352 #else
353 const size_t length = 1;
355 memmove(wr->line + wr->cursor + length,
356 wr->line + wr->cursor, rest);
357 wr->line[wr->cursor] = key;
359 #endif
361 wr->cursor += length;
362 if (cursor_column(wr) >= wr->width)
363 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
364 }
366 static void
367 wreadln_delete_char(struct wreadln *wr, size_t x)
368 {
369 size_t rest, length;
371 assert(x < strlen(wr->line));
373 length = next_char_size(&wr->line[x]);
374 rest = strlen(&wr->line[x + length]) + 1;
375 memmove(&wr->line[x], &wr->line[x + length], rest);
376 }
378 /* libcurses version */
380 static gchar *
381 _wreadln(WINDOW *w,
382 const gchar *prompt,
383 const gchar *initial_value,
384 unsigned x1,
385 GList **history,
386 GCompletion *gcmp,
387 gboolean masked)
388 {
389 struct wreadln wr = {
390 .w = w,
391 .masked = masked,
392 .cursor = 0,
393 .start = 0,
394 };
395 GList *hlist = NULL, *hcurrent = NULL;
396 gint key = 0;
397 size_t i;
399 /* turn off echo */
400 noecho();
401 /* make shure the cursor is visible */
402 curs_set(1);
403 /* print prompt string */
404 if (prompt)
405 waddstr(w, prompt);
406 /* retrive y and x0 position */
407 getyx(w, wr.y, wr.x);
408 /* check the x1 value */
409 if (x1 <= wr.x || x1 > (unsigned)COLS)
410 x1 = COLS;
411 wr.width = x1 - wr.x;
412 /* clear input area */
413 mvwhline(w, wr.y, wr.x, ' ', wr.width);
415 if (history) {
416 /* append the a new line to our history list */
417 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
418 /* hlist points to the current item in the history list */
419 hlist = g_list_last(*history);
420 hcurrent = hlist;
421 }
423 if (initial_value == (char *)-1) {
424 /* get previous history entry */
425 if (history && hlist->prev) {
426 if (hlist == hcurrent)
427 /* save the current line */
428 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
430 /* get previous line */
431 hlist = hlist->prev;
432 g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
433 }
434 cursor_move_to_eol(&wr);
435 drawline(&wr);
436 } else if (initial_value) {
437 /* copy the initial value to the line buffer */
438 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
439 cursor_move_to_eol(&wr);
440 drawline(&wr);
441 }
443 while (key != 13 && key != '\n') {
444 key = wgetch(w);
446 /* check if key is a function key */
447 for (i = 0; i < 63; i++)
448 if (key == (int)KEY_F(i)) {
449 key = KEY_F(1);
450 i = 64;
451 }
453 switch (key) {
454 #ifdef HAVE_GETMOUSE
455 case KEY_MOUSE: /* ignore mouse events */
456 #endif
457 case ERR: /* ingnore errors */
458 break;
460 case TAB:
461 if (gcmp) {
462 char *prefix = NULL;
463 GList *list;
465 if (wrln_pre_completion_callback)
466 wrln_pre_completion_callback(gcmp, wr.line,
467 wrln_completion_callback_data);
468 list = g_completion_complete(gcmp, wr.line, &prefix);
469 if (prefix) {
470 g_strlcpy(wr.line, prefix, sizeof(wr.line));
471 cursor_move_to_eol(&wr);
472 g_free(prefix);
473 } else
474 screen_bell();
476 if (wrln_post_completion_callback)
477 wrln_post_completion_callback(gcmp, wr.line, list,
478 wrln_completion_callback_data);
479 }
480 break;
482 case KEY_CTRL_G:
483 screen_bell();
484 if (history) {
485 g_free(hcurrent->data);
486 hcurrent->data = NULL;
487 *history = g_list_delete_link(*history, hcurrent);
488 }
489 return NULL;
491 case KEY_LEFT:
492 case KEY_CTRL_B:
493 cursor_move_left(&wr);
494 break;
495 case KEY_RIGHT:
496 case KEY_CTRL_F:
497 cursor_move_right(&wr);
498 break;
499 case KEY_HOME:
500 case KEY_CTRL_A:
501 wr.cursor = 0;
502 wr.start = 0;
503 break;
504 case KEY_END:
505 case KEY_CTRL_E:
506 cursor_move_to_eol(&wr);
507 break;
508 case KEY_CTRL_K:
509 wr.line[wr.cursor] = 0;
510 break;
511 case KEY_CTRL_U:
512 wr.cursor = utf8_width(wr.line);
513 for (i = 0; i < wr.cursor; i++)
514 wr.line[i] = '\0';
515 wr.cursor = 0;
516 break;
517 case 127:
518 case KEY_BCKSPC: /* handle backspace: copy all */
519 case KEY_BACKSPACE: /* chars starting from curpos */
520 if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf */
521 cursor_move_left(&wr);
522 wreadln_delete_char(&wr, wr.cursor);
523 }
524 break;
525 case KEY_DC: /* handle delete key. As above */
526 case KEY_CTRL_D:
527 if (wr.line[wr.cursor] != 0)
528 wreadln_delete_char(&wr, wr.cursor);
529 break;
530 case KEY_UP:
531 case KEY_CTRL_P:
532 /* get previous history entry */
533 if (history && hlist->prev) {
534 if (hlist == hcurrent)
535 /* save the current line */
536 g_strlcpy(hlist->data, wr.line,
537 sizeof(wr.line));
539 /* get previous line */
540 hlist = hlist->prev;
541 g_strlcpy(wr.line, hlist->data,
542 sizeof(wr.line));
543 }
544 cursor_move_to_eol(&wr);
545 break;
546 case KEY_DOWN:
547 case KEY_CTRL_N:
548 /* get next history entry */
549 if (history && hlist->next) {
550 /* get next line */
551 hlist = hlist->next;
552 g_strlcpy(wr.line, hlist->data,
553 sizeof(wr.line));
554 }
555 cursor_move_to_eol(&wr);
556 break;
558 case '\n':
559 case 13:
560 case KEY_IC:
561 case KEY_PPAGE:
562 case KEY_NPAGE:
563 case KEY_F(1):
564 /* ignore char */
565 break;
566 default:
567 if (key >= 32)
568 wreadln_insert_byte(&wr, key);
569 }
571 drawline(&wr);
572 }
574 /* update history */
575 if (history) {
576 if (strlen(wr.line)) {
577 /* update the current history entry */
578 size_t size = strlen(wr.line) + 1;
579 hcurrent->data = g_realloc(hcurrent->data, size);
580 g_strlcpy(hcurrent->data, wr.line, size);
581 } else {
582 /* the line was empty - remove the current history entry */
583 g_free(hcurrent->data);
584 hcurrent->data = NULL;
585 *history = g_list_delete_link(*history, hcurrent);
586 }
588 while (g_list_length(*history) > wrln_max_history_length) {
589 GList *first = g_list_first(*history);
591 /* remove the oldest history entry */
592 g_free(first->data);
593 first->data = NULL;
594 *history = g_list_delete_link(*history, first);
595 }
596 }
598 return g_strdup(wr.line);
599 }
601 gchar *
602 wreadln(WINDOW *w,
603 const gchar *prompt,
604 const gchar *initial_value,
605 unsigned x1,
606 GList **history,
607 GCompletion *gcmp)
608 {
609 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
610 }
612 gchar *
613 wreadln_masked(WINDOW *w,
614 const gchar *prompt,
615 const gchar *initial_value,
616 unsigned x1,
617 GList **history,
618 GCompletion *gcmp)
619 {
620 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
621 }