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