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