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