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 "config.h"
23 #include <stdlib.h>
24 #include <string.h>
25 #include <glib.h>
27 #ifdef USE_NCURSESW
28 #include <ncursesw/ncurses.h>
29 #else
30 #include <ncurses.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 #define WRLN_MAX_LINE_SIZE 1024
49 #define WRLN_MAX_HISTORY_LENGTH 32
51 guint wrln_max_line_size = WRLN_MAX_LINE_SIZE;
52 guint wrln_max_history_length = WRLN_MAX_HISTORY_LENGTH;
53 wrln_wgetch_fn_t wrln_wgetch = NULL;
54 void *wrln_completion_callback_data = NULL;
55 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
56 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
58 extern void sigstop(void);
59 extern void screen_bell(void);
61 #ifndef USE_NCURSESW
62 /* move the cursor one step to the right */
63 static inline void cursor_move_right(gint *cursor,
64 gint *start,
65 gint width,
66 gint x0,
67 gint x1,
68 gchar *line)
69 {
70 if (*cursor < (int)strlen(line) &&
71 *cursor < (int)wrln_max_line_size - 1) {
72 (*cursor)++;
73 if (*cursor + x0 >= x1 && *start < *cursor - width + 1)
74 (*start)++;
75 }
76 }
78 /* move the cursor one step to the left */
79 static inline void cursor_move_left(gint *cursor,
80 gint *start)
81 {
82 if( *cursor > 0 )
83 {
84 if( *cursor==*start && *start > 0 )
85 (*start)--;
86 (*cursor)--;
87 }
88 }
90 /* move the cursor to the end of the line */
91 static inline void cursor_move_to_eol(gint *cursor,
92 gint *start,
93 gint width,
94 gint x0,
95 gint x1,
96 gchar *line)
97 {
98 *cursor = strlen(line);
99 if( *cursor+x0 >= x1 )
100 *start = *cursor-width+1;
101 }
103 /* draw line buffer and update cursor position */
104 static inline void drawline(gint cursor,
105 gint start,
106 gint width,
107 gint x0,
108 gint y,
109 gboolean masked,
110 gchar *line,
111 WINDOW *w)
112 {
113 wmove(w, y, x0);
114 /* clear input area */
115 whline(w, ' ', width);
116 /* print visible part of the line buffer */
117 if(masked == TRUE) whline(w, '*', utf8_width(line) - start);
118 else waddnstr(w, line+start, width);
119 /* move the cursor to the correct position */
120 wmove(w, y, x0 + cursor-start);
121 /* tell ncurses to redraw the screen */
122 doupdate();
123 }
126 /* libcurses version */
128 static gchar *
129 _wreadln(WINDOW *w,
130 const gchar *prompt,
131 const gchar *initial_value,
132 gint x1,
133 GList **history,
134 GCompletion *gcmp,
135 gboolean masked)
136 {
137 GList *hlist = NULL, *hcurrent = NULL;
138 gchar *line;
139 gint x0, y, width;
140 gint cursor = 0, start = 0;
141 gint key = 0, i;
143 /* allocate a line buffer */
144 line = g_malloc0(wrln_max_line_size);
145 /* turn off echo */
146 noecho();
147 /* make shure the cursor is visible */
148 curs_set(1);
149 /* print prompt string */
150 if( prompt )
151 waddstr(w, prompt);
152 /* retrive y and x0 position */
153 getyx(w, y, x0);
154 /* check the x1 value */
155 if( x1<=x0 || x1>COLS )
156 x1 = COLS;
157 width = x1-x0;
158 /* clear input area */
159 mvwhline(w, y, x0, ' ', width);
161 if( history ) {
162 /* append the a new line to our history list */
163 *history = g_list_append(*history, g_malloc0(wrln_max_line_size));
164 /* hlist points to the current item in the history list */
165 hlist = g_list_last(*history);
166 hcurrent = hlist;
167 }
169 if( initial_value == (char *) -1 ) {
170 /* get previous history entry */
171 if( history && hlist->prev )
172 {
173 if( hlist==hcurrent )
174 {
175 /* save the current line */
176 g_strlcpy(hlist->data, line, wrln_max_line_size);
177 }
178 /* get previous line */
179 hlist = hlist->prev;
180 g_strlcpy(line, hlist->data, wrln_max_line_size);
181 }
182 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
183 drawline(cursor, start, width, x0, y, masked, line, w);
184 } else if( initial_value ) {
185 /* copy the initial value to the line buffer */
186 g_strlcpy(line, initial_value, wrln_max_line_size);
187 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
188 drawline(cursor, start, width, x0, y, masked, line, w);
189 }
191 while( key!=13 && key!='\n' ) {
192 if( wrln_wgetch )
193 key = wrln_wgetch(w);
194 else
195 key = wgetch(w);
197 /* check if key is a function key */
198 for(i=0; i<63; i++)
199 if( key==KEY_F(i) ) {
200 key=KEY_F(1);
201 i=64;
202 }
204 switch (key) {
205 #ifdef HAVE_GETMOUSE
206 case KEY_MOUSE: /* ignore mouse events */
207 #endif
208 case ERR: /* ingnore errors */
209 break;
211 case KEY_RESIZE:
212 /* a resize event */
213 if( x1>COLS ) {
214 x1=COLS;
215 width = x1-x0;
216 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
217 }
218 /* make shure the cursor is visible */
219 curs_set(1);
220 break;
222 case TAB:
223 if( gcmp ) {
224 char *prefix = NULL;
225 GList *list;
227 if(wrln_pre_completion_callback)
228 wrln_pre_completion_callback(gcmp, line,
229 wrln_completion_callback_data);
230 list = g_completion_complete(gcmp, line, &prefix);
231 if( prefix ) {
232 g_strlcpy(line, prefix, wrln_max_line_size);
233 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
234 g_free(prefix);
235 }
236 else
237 screen_bell();
238 if( wrln_post_completion_callback )
239 wrln_post_completion_callback(gcmp, line, list,
240 wrln_completion_callback_data);
241 }
242 break;
244 case KEY_CTRL_G:
245 screen_bell();
246 g_free(line);
247 if( history ) {
248 g_free(hcurrent->data);
249 hcurrent->data = NULL;
250 *history = g_list_delete_link(*history, hcurrent);
251 }
252 return NULL;
254 case KEY_LEFT:
255 case KEY_CTRL_B:
256 cursor_move_left(&cursor, &start);
257 break;
258 case KEY_RIGHT:
259 case KEY_CTRL_F:
260 cursor_move_right(&cursor, &start, width, x0, x1, line);
261 break;
262 case KEY_HOME:
263 case KEY_CTRL_A:
264 cursor = 0;
265 start = 0;
266 break;
267 case KEY_END:
268 case KEY_CTRL_E:
269 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
270 break;
271 case KEY_CTRL_K:
272 line[cursor] = 0;
273 break;
274 case KEY_CTRL_U:
275 cursor = utf8_width(line);
276 for (i = 0;i < cursor; i++)
277 line[i] = '\0';
278 cursor = 0;
279 break;
280 case 127:
281 case KEY_BCKSPC: /* handle backspace: copy all */
282 case KEY_BACKSPACE: /* chars starting from curpos */
283 if( cursor > 0 ) {/* - 1 from buf[n+1] to buf */
284 for (i = cursor - 1; line[i] != 0; i++)
285 line[i] = line[i + 1];
286 cursor_move_left(&cursor, &start);
287 }
288 break;
289 case KEY_DC: /* handle delete key. As above */
290 case KEY_CTRL_D:
291 if (cursor <= (gint)utf8_width(line) - 1) {
292 for (i = cursor; line[i] != 0; i++)
293 line[i] = line[i + 1];
294 }
295 break;
296 case KEY_UP:
297 case KEY_CTRL_P:
298 /* get previous history entry */
299 if( history && hlist->prev ) {
300 if( hlist==hcurrent )
301 {
302 /* save the current line */
303 g_strlcpy(hlist->data, line, wrln_max_line_size);
304 }
305 /* get previous line */
306 hlist = hlist->prev;
307 g_strlcpy(line, hlist->data, wrln_max_line_size);
308 }
309 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
310 break;
311 case KEY_DOWN:
312 case KEY_CTRL_N:
313 /* get next history entry */
314 if( history && hlist->next ) {
315 /* get next line */
316 hlist = hlist->next;
317 g_strlcpy(line, hlist->data, wrln_max_line_size);
318 }
319 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
320 break;
322 case '\n':
323 case 13:
324 case KEY_IC:
325 case KEY_PPAGE:
326 case KEY_NPAGE:
327 case KEY_F(1):
328 /* ignore char */
329 break;
330 default:
331 if (key >= 32) {
332 if (strlen (line + cursor)) { /* if the cursor is */
333 /* not at the last pos */
334 gchar *tmp = NULL;
335 gsize size = strlen(line + cursor) + 1;
337 tmp = g_malloc0(size);
338 g_strlcpy (tmp, line + cursor, size);
339 line[cursor] = key;
340 line[cursor + 1] = 0;
341 g_strlcat (&line[cursor + 1], tmp, size);
342 g_free(tmp);
343 cursor_move_right(&cursor, &start, width, x0, x1, line);
344 } else {
345 line[cursor + 1] = 0;
346 line[cursor] = key;
347 cursor_move_right(&cursor, &start, width, x0, x1, line);
348 }
349 }
350 }
352 drawline(cursor, start, width, x0, y, masked, line, w);
353 }
355 /* update history */
356 if( history ) {
357 if( strlen(line) ) {
358 /* update the current history entry */
359 size_t size = strlen(line)+1;
360 hcurrent->data = g_realloc(hcurrent->data, size);
361 g_strlcpy(hcurrent->data, line, size);
362 } else {
363 /* the line was empty - remove the current history entry */
364 g_free(hcurrent->data);
365 hcurrent->data = NULL;
366 *history = g_list_delete_link(*history, hcurrent);
367 }
369 while( g_list_length(*history) > wrln_max_history_length ) {
370 GList *first = g_list_first(*history);
372 /* remove the oldest history entry */
373 g_free(first->data);
374 first->data = NULL;
375 *history = g_list_delete_link(*history, first);
376 }
377 }
379 return g_realloc(line, strlen(line)+1);
380 }
382 #else
384 /* move the cursor one step to the right */
385 static inline void cursor_move_right(gint *cursor,
386 gint *start,
387 gint width,
388 gint x0,
389 gint x1,
390 wchar_t *wline)
391 {
392 if( *cursor < wcslen(wline) && *cursor<wrln_max_line_size-1 )
393 {
394 (*cursor)++;
395 if( *cursor+x0 >= x1 && *start<*cursor-width+1)
396 (*start)++;
397 }
398 }
400 /* move the cursor one step to the left */
401 static inline void cursor_move_left(gint *cursor,
402 gint *start,
403 gint width,
404 gint x0,
405 gint x1,
406 wchar_t *line)
407 {
408 if( *cursor > 0 )
409 {
410 if( *cursor==*start && *start > 0 )
411 (*start)--;
412 (*cursor)--;
413 }
414 }
417 static inline void backspace(gint *cursor,
418 gint *start,
419 gint width,
420 gint x0,
421 gint x1,
422 wchar_t *wline)
423 {
424 int i;
425 if( *cursor > 0 )
426 {
427 for (i = *cursor - 1; wline[i] != 0; i++)
428 wline[i] = wline[i + 1];
429 cursor_move_left(cursor, start, width, x0, x1, wline);
430 }
431 }
433 /* handle delete */
434 static inline void delete(gint *cursor,
435 wchar_t *wline)
436 {
437 int i;
438 if( *cursor <= wcslen(wline) - 1 )
439 {
440 for (i = *cursor; wline[i] != 0; i++)
441 wline[i] = wline[i + 1];
442 }
443 }
445 /* move the cursor to the end of the line */
446 static inline void cursor_move_to_eol(gint *cursor,
447 gint *start,
448 gint width,
449 gint x0,
450 gint x1,
451 wchar_t *line)
452 {
453 *cursor = wcslen(line);
454 if( *cursor+x0 >= x1 )
455 *start = *cursor-width+1;
456 }
458 /* draw line buffer and update cursor position */
459 static inline void drawline(gint cursor,
460 gint start,
461 gint width,
462 gint x0,
463 gint y,
464 gboolean masked,
465 wchar_t *line,
466 WINDOW *w)
467 {
468 wmove(w, y, x0);
469 /* clear input area */
470 whline(w, ' ', width);
471 /* print visible part of the line buffer */
472 if(masked == TRUE) whline(w, '*', wcslen(line)-start);
473 else waddnwstr(w, line+start, width);
474 FILE *dbg = fopen ("dbg", "a+");
475 fprintf (dbg, "%i,%s---%i---", width, line, wcslen (line));
476 /* move the cursor to the correct position */
477 wmove(w, y, x0 + cursor-start);
478 /* tell ncurses to redraw the screen */
479 doupdate();
480 }
482 /* libcursesw version */
484 static gchar *
485 _wreadln(WINDOW *w,
486 const gchar *prompt,
487 const gchar *initial_value,
488 gint x1,
489 GList **history,
490 GCompletion *gcmp,
491 gboolean masked)
492 {
493 GList *hlist = NULL, *hcurrent = NULL;
494 wchar_t *wline;
495 gchar *mbline;
496 gint x0, x, y, width, start;
497 gint cursor;
498 wint_t wch;
499 gint key;
500 gint i;
502 /* initialize variables */
503 start = 0;
504 x = 0;
505 cursor = 0;
506 mbline = NULL;
508 /* allocate a line buffer */
509 wline = g_malloc0(wrln_max_line_size*sizeof(wchar_t));
510 /* turn off echo */
511 noecho();
512 /* make shure the cursor is visible */
513 curs_set(1);
514 /* print prompt string */
515 if( prompt )
516 waddstr(w, prompt);
517 /* retrive y and x0 position */
518 getyx(w, y, x0);
519 /* check the x1 value */
520 if( x1<=x0 || x1>COLS )
521 x1 = COLS;
522 width = x1-x0;
523 /* clear input area */
524 mvwhline(w, y, x0, ' ', width);
527 if( history )
528 {
529 /* append the a new line to our history list */
530 *history = g_list_append(*history, g_malloc0(wrln_max_line_size));
531 /* hlist points to the current item in the history list */
532 hlist = g_list_last(*history);
533 hcurrent = hlist;
534 }
535 if( initial_value == (char *) -1 )
536 {
537 /* get previous history entry */
538 if( history && hlist->prev )
539 {
540 if( hlist==hcurrent )
541 {
542 /* save the current line */
543 //g_strlcpy(hlist->data, line, wrln_max_line_size);
544 }
545 /* get previous line */
546 hlist = hlist->prev;
547 mbstowcs(wline, hlist->data, wrln_max_line_size);
548 }
549 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
550 drawline(cursor, start, width, x0, y, masked, wline, w);
551 }
552 else if( initial_value )
553 {
554 /* copy the initial value to the line buffer */
555 mbstowcs(wline, initial_value, wrln_max_line_size);
556 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
557 drawline(cursor, start, width, x0, y, masked, wline, w);
558 }
560 wch=0;
561 key=0;
562 while( wch!=13 && wch!='\n' )
563 {
564 key = wget_wch(w, &wch);
566 if( key==KEY_CODE_YES )
567 {
568 /* function key */
569 switch(wch)
570 {
571 case KEY_HOME:
572 x=0;
573 cursor=0;
574 start=0;
575 break;
576 case KEY_END:
577 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
578 break;
579 case KEY_LEFT:
580 cursor_move_left(&cursor, &start, width, x0, x1, wline);
581 break;
582 case KEY_RIGHT:
583 cursor_move_right(&cursor, &start, width, x0, x1, wline);
584 break;
585 case KEY_DC:
586 delete(&cursor, wline);
587 break;
588 case KEY_BCKSPC:
589 case KEY_BACKSPACE:
590 backspace(&cursor, &start, width, x0, x1, wline);
591 break;
592 case KEY_UP:
593 /* get previous history entry */
594 if( history && hlist->prev )
595 {
596 if( hlist==hcurrent )
597 {
598 /* save the current line */
599 wcstombs(hlist->data, wline, wrln_max_line_size);
600 }
601 /* get previous line */
602 hlist = hlist->prev;
603 mbstowcs(wline, hlist->data, wrln_max_line_size);
604 }
605 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
606 break;
607 case KEY_DOWN:
608 /* get next history entry */
609 if( history && hlist->next )
610 {
611 /* get next line */
612 hlist = hlist->next;
613 mbstowcs(wline, hlist->data, wrln_max_line_size);
614 }
615 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
616 break;
617 case KEY_RESIZE:
618 /* resize event */
619 if( x1>COLS )
620 {
621 x1=COLS;
622 width = x1-x0;
623 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
624 }
625 /* make shure the cursor is visible */
626 curs_set(1);
627 break;
628 }
630 }
631 else if( key!=ERR )
632 {
633 switch(wch)
634 {
635 case KEY_CTRL_A:
636 x=0;
637 cursor=0;
638 start=0;
639 break;
640 case KEY_CTRL_C:
641 exit(EXIT_SUCCESS);
642 break;
643 case KEY_CTRL_D:
644 delete(&cursor, wline);
645 break;
646 case KEY_CTRL_E:
647 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
648 break;
649 case TAB:
650 if( gcmp )
651 {
652 char *prefix = NULL;
653 GList *list;
655 i = wcstombs(NULL,wline,0)+1;
656 mbline = g_malloc0(i);
657 wcstombs(mbline, wline, i);
659 if(wrln_pre_completion_callback)
660 wrln_pre_completion_callback(gcmp, mbline,
661 wrln_completion_callback_data);
662 list = g_completion_complete(gcmp, mbline, &prefix);
663 if( prefix )
664 {
665 mbstowcs(wline, prefix, wrln_max_line_size);
666 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
667 g_free(prefix);
668 }
669 else
670 screen_bell();
671 if( wrln_post_completion_callback )
672 wrln_post_completion_callback(gcmp, mbline, list,
673 wrln_completion_callback_data);
675 g_free(mbline);
676 }
677 break;
678 case KEY_CTRL_G:
679 screen_bell();
680 g_free(wline);
681 if( history )
682 {
683 g_free(hcurrent->data);
684 hcurrent->data = NULL;
685 *history = g_list_delete_link(*history, hcurrent);
686 }
687 return NULL;
688 case KEY_CTRL_K:
689 wline[cursor] = 0;
690 break;
691 case KEY_CTRL_U:
692 cursor = wcslen(wline);
693 for (i = 0;i < cursor; i++)
694 wline[i] = '\0';
695 cursor = 0;
696 break;
697 case KEY_CTRL_Z:
698 sigstop();
699 break;
700 case 127:
701 backspace(&cursor, &start, width, x0, x1, wline);
702 break;
703 case '\n':
704 case 13:
705 /* ignore char */
706 break;
707 default:
708 if( (wcslen(wline+cursor)) )
709 {
710 /* the cursor is not at the last pos */
711 wchar_t *tmp = NULL;
712 gsize len = (wcslen(wline+cursor)+1);
713 tmp = g_malloc0(len*sizeof(wchar_t));
714 wmemcpy(tmp, wline+cursor, len);
715 wline[cursor] = wch;
716 wline[cursor+1] = 0;
717 wcscat(&wline[cursor+1], tmp);
718 g_free(tmp);
719 cursor_move_right(&cursor, &start, width, x0, x1, wline);
720 }
721 else
722 {
723 FILE *ff = fopen ("curspr", "a+");
724 fprintf (ff, "%i", cursor);
725 wline[cursor] = wch;
726 wline[cursor+1] = 0;
727 cursor_move_right(&cursor, &start, width, x0, x1, wline);
728 }
729 }
730 }
731 drawline(cursor, start, width, x0, y, masked, wline, w);
732 }
733 i = wcstombs(NULL,wline,0)+1;
734 mbline = g_malloc0(i);
735 wcstombs(mbline, wline, i);
737 /* update history */
738 if( history )
739 {
740 if( strlen(mbline) )
741 {
742 /* update the current history entry */
743 size_t size = strlen(mbline)+1;
744 hcurrent->data = g_realloc(hcurrent->data, size);
745 g_strlcpy(hcurrent->data, mbline, size);
746 }
747 else
748 {
749 /* the line was empty - remove the current history entry */
750 g_free(hcurrent->data);
751 hcurrent->data = NULL;
752 *history = g_list_delete_link(*history, hcurrent);
753 }
755 while( g_list_length(*history) > wrln_max_history_length )
756 {
757 GList *first = g_list_first(*history);
759 /* remove the oldest history entry */
760 g_free(first->data);
761 first->data = NULL;
762 *history = g_list_delete_link(*history, first);
763 }
764 }
765 return mbline;
766 }
768 #endif
770 gchar *
771 wreadln(WINDOW *w,
772 const gchar *prompt,
773 const gchar *initial_value,
774 gint x1,
775 GList **history,
776 GCompletion *gcmp)
777 {
778 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
779 }
781 gchar *
782 wreadln_masked(WINDOW *w,
783 const gchar *prompt,
784 const gchar *initial_value,
785 gint x1,
786 GList **history,
787 GCompletion *gcmp)
788 {
789 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
790 }