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