bf31d1db349edbaadb6114f7400b8d8c1c7e2b87
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 TAB:
212 if( gcmp ) {
213 char *prefix = NULL;
214 GList *list;
216 if(wrln_pre_completion_callback)
217 wrln_pre_completion_callback(gcmp, line,
218 wrln_completion_callback_data);
219 list = g_completion_complete(gcmp, line, &prefix);
220 if( prefix ) {
221 g_strlcpy(line, prefix, wrln_max_line_size);
222 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
223 g_free(prefix);
224 }
225 else
226 screen_bell();
227 if( wrln_post_completion_callback )
228 wrln_post_completion_callback(gcmp, line, list,
229 wrln_completion_callback_data);
230 }
231 break;
233 case KEY_CTRL_G:
234 screen_bell();
235 g_free(line);
236 if( history ) {
237 g_free(hcurrent->data);
238 hcurrent->data = NULL;
239 *history = g_list_delete_link(*history, hcurrent);
240 }
241 return NULL;
243 case KEY_LEFT:
244 case KEY_CTRL_B:
245 cursor_move_left(&cursor, &start);
246 break;
247 case KEY_RIGHT:
248 case KEY_CTRL_F:
249 cursor_move_right(&cursor, &start, width, x0, x1, line);
250 break;
251 case KEY_HOME:
252 case KEY_CTRL_A:
253 cursor = 0;
254 start = 0;
255 break;
256 case KEY_END:
257 case KEY_CTRL_E:
258 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
259 break;
260 case KEY_CTRL_K:
261 line[cursor] = 0;
262 break;
263 case KEY_CTRL_U:
264 cursor = utf8_width(line);
265 for (i = 0;i < cursor; i++)
266 line[i] = '\0';
267 cursor = 0;
268 break;
269 case 127:
270 case KEY_BCKSPC: /* handle backspace: copy all */
271 case KEY_BACKSPACE: /* chars starting from curpos */
272 if( cursor > 0 ) {/* - 1 from buf[n+1] to buf */
273 for (i = cursor - 1; line[i] != 0; i++)
274 line[i] = line[i + 1];
275 cursor_move_left(&cursor, &start);
276 }
277 break;
278 case KEY_DC: /* handle delete key. As above */
279 case KEY_CTRL_D:
280 if (cursor <= (gint)utf8_width(line) - 1) {
281 for (i = cursor; line[i] != 0; i++)
282 line[i] = line[i + 1];
283 }
284 break;
285 case KEY_UP:
286 case KEY_CTRL_P:
287 /* get previous history entry */
288 if( history && hlist->prev ) {
289 if( hlist==hcurrent )
290 {
291 /* save the current line */
292 g_strlcpy(hlist->data, line, wrln_max_line_size);
293 }
294 /* get previous line */
295 hlist = hlist->prev;
296 g_strlcpy(line, hlist->data, wrln_max_line_size);
297 }
298 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
299 break;
300 case KEY_DOWN:
301 case KEY_CTRL_N:
302 /* get next history entry */
303 if( history && hlist->next ) {
304 /* get next line */
305 hlist = hlist->next;
306 g_strlcpy(line, hlist->data, wrln_max_line_size);
307 }
308 cursor_move_to_eol(&cursor, &start, width, x0, x1, line);
309 break;
311 case '\n':
312 case 13:
313 case KEY_IC:
314 case KEY_PPAGE:
315 case KEY_NPAGE:
316 case KEY_F(1):
317 /* ignore char */
318 break;
319 default:
320 if (key >= 32) {
321 if (strlen (line + cursor)) { /* if the cursor is */
322 /* not at the last pos */
323 gchar *tmp = NULL;
324 gsize size = strlen(line + cursor) + 1;
326 tmp = g_malloc0(size);
327 g_strlcpy (tmp, line + cursor, size);
328 line[cursor] = key;
329 line[cursor + 1] = 0;
330 g_strlcat (&line[cursor + 1], tmp, size);
331 g_free(tmp);
332 cursor_move_right(&cursor, &start, width, x0, x1, line);
333 } else {
334 line[cursor + 1] = 0;
335 line[cursor] = key;
336 cursor_move_right(&cursor, &start, width, x0, x1, line);
337 }
338 }
339 }
341 drawline(cursor, start, width, x0, y, masked, line, w);
342 }
344 /* update history */
345 if( history ) {
346 if( strlen(line) ) {
347 /* update the current history entry */
348 size_t size = strlen(line)+1;
349 hcurrent->data = g_realloc(hcurrent->data, size);
350 g_strlcpy(hcurrent->data, line, size);
351 } else {
352 /* the line was empty - remove the current history entry */
353 g_free(hcurrent->data);
354 hcurrent->data = NULL;
355 *history = g_list_delete_link(*history, hcurrent);
356 }
358 while( g_list_length(*history) > wrln_max_history_length ) {
359 GList *first = g_list_first(*history);
361 /* remove the oldest history entry */
362 g_free(first->data);
363 first->data = NULL;
364 *history = g_list_delete_link(*history, first);
365 }
366 }
368 return g_realloc(line, strlen(line)+1);
369 }
371 #else
373 /* move the cursor one step to the right */
374 static inline void cursor_move_right(gint *cursor,
375 gint *start,
376 gint width,
377 gint x0,
378 gint x1,
379 wchar_t *wline)
380 {
381 if( *cursor < wcslen(wline) && *cursor<wrln_max_line_size-1 )
382 {
383 (*cursor)++;
384 if( *cursor+x0 >= x1 && *start<*cursor-width+1)
385 (*start)++;
386 }
387 }
389 /* move the cursor one step to the left */
390 static inline void cursor_move_left(gint *cursor,
391 gint *start,
392 gint width,
393 gint x0,
394 gint x1,
395 wchar_t *line)
396 {
397 if( *cursor > 0 )
398 {
399 if( *cursor==*start && *start > 0 )
400 (*start)--;
401 (*cursor)--;
402 }
403 }
406 static inline void backspace(gint *cursor,
407 gint *start,
408 gint width,
409 gint x0,
410 gint x1,
411 wchar_t *wline)
412 {
413 int i;
414 if( *cursor > 0 )
415 {
416 for (i = *cursor - 1; wline[i] != 0; i++)
417 wline[i] = wline[i + 1];
418 cursor_move_left(cursor, start, width, x0, x1, wline);
419 }
420 }
422 /* handle delete */
423 static inline void delete(gint *cursor,
424 wchar_t *wline)
425 {
426 int i;
427 if( *cursor <= wcslen(wline) - 1 )
428 {
429 for (i = *cursor; wline[i] != 0; i++)
430 wline[i] = wline[i + 1];
431 }
432 }
434 /* move the cursor to the end of the line */
435 static inline void cursor_move_to_eol(gint *cursor,
436 gint *start,
437 gint width,
438 gint x0,
439 gint x1,
440 wchar_t *line)
441 {
442 *cursor = wcslen(line);
443 if( *cursor+x0 >= x1 )
444 *start = *cursor-width+1;
445 }
447 /* draw line buffer and update cursor position */
448 static inline void drawline(gint cursor,
449 gint start,
450 gint width,
451 gint x0,
452 gint y,
453 gboolean masked,
454 wchar_t *line,
455 WINDOW *w)
456 {
457 wmove(w, y, x0);
458 /* clear input area */
459 whline(w, ' ', width);
460 /* print visible part of the line buffer */
461 if(masked == TRUE) whline(w, '*', wcslen(line)-start);
462 else waddnwstr(w, line+start, width);
463 FILE *dbg = fopen ("dbg", "a+");
464 fprintf (dbg, "%i,%s---%i---", width, line, wcslen (line));
465 /* move the cursor to the correct position */
466 wmove(w, y, x0 + cursor-start);
467 /* tell ncurses to redraw the screen */
468 doupdate();
469 }
471 /* libcursesw version */
473 static gchar *
474 _wreadln(WINDOW *w,
475 const gchar *prompt,
476 const gchar *initial_value,
477 gint x1,
478 GList **history,
479 GCompletion *gcmp,
480 gboolean masked)
481 {
482 GList *hlist = NULL, *hcurrent = NULL;
483 wchar_t *wline;
484 gchar *mbline;
485 gint x0, x, y, width, start;
486 gint cursor;
487 wint_t wch;
488 gint key;
489 gint i;
491 /* initialize variables */
492 start = 0;
493 x = 0;
494 cursor = 0;
495 mbline = NULL;
497 /* allocate a line buffer */
498 wline = g_malloc0(wrln_max_line_size*sizeof(wchar_t));
499 /* turn off echo */
500 noecho();
501 /* make shure the cursor is visible */
502 curs_set(1);
503 /* print prompt string */
504 if( prompt )
505 waddstr(w, prompt);
506 /* retrive y and x0 position */
507 getyx(w, y, x0);
508 /* check the x1 value */
509 if( x1<=x0 || x1>COLS )
510 x1 = COLS;
511 width = x1-x0;
512 /* clear input area */
513 mvwhline(w, y, x0, ' ', width);
516 if( history )
517 {
518 /* append the a new line to our history list */
519 *history = g_list_append(*history, g_malloc0(wrln_max_line_size));
520 /* hlist points to the current item in the history list */
521 hlist = g_list_last(*history);
522 hcurrent = hlist;
523 }
524 if( initial_value == (char *) -1 )
525 {
526 /* get previous history entry */
527 if( history && hlist->prev )
528 {
529 if( hlist==hcurrent )
530 {
531 /* save the current line */
532 //g_strlcpy(hlist->data, line, wrln_max_line_size);
533 }
534 /* get previous line */
535 hlist = hlist->prev;
536 mbstowcs(wline, hlist->data, wrln_max_line_size);
537 }
538 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
539 drawline(cursor, start, width, x0, y, masked, wline, w);
540 }
541 else if( initial_value )
542 {
543 /* copy the initial value to the line buffer */
544 mbstowcs(wline, initial_value, wrln_max_line_size);
545 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
546 drawline(cursor, start, width, x0, y, masked, wline, w);
547 }
549 wch=0;
550 key=0;
551 while( wch!=13 && wch!='\n' )
552 {
553 key = wget_wch(w, &wch);
555 if( key==KEY_CODE_YES )
556 {
557 /* function key */
558 switch(wch)
559 {
560 case KEY_HOME:
561 x=0;
562 cursor=0;
563 start=0;
564 break;
565 case KEY_END:
566 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
567 break;
568 case KEY_LEFT:
569 cursor_move_left(&cursor, &start, width, x0, x1, wline);
570 break;
571 case KEY_RIGHT:
572 cursor_move_right(&cursor, &start, width, x0, x1, wline);
573 break;
574 case KEY_DC:
575 delete(&cursor, wline);
576 break;
577 case KEY_BCKSPC:
578 case KEY_BACKSPACE:
579 backspace(&cursor, &start, width, x0, x1, wline);
580 break;
581 case KEY_UP:
582 /* get previous history entry */
583 if( history && hlist->prev )
584 {
585 if( hlist==hcurrent )
586 {
587 /* save the current line */
588 wcstombs(hlist->data, wline, wrln_max_line_size);
589 }
590 /* get previous line */
591 hlist = hlist->prev;
592 mbstowcs(wline, hlist->data, wrln_max_line_size);
593 }
594 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
595 break;
596 case KEY_DOWN:
597 /* get next history entry */
598 if( history && hlist->next )
599 {
600 /* get next line */
601 hlist = hlist->next;
602 mbstowcs(wline, hlist->data, wrln_max_line_size);
603 }
604 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
605 break;
606 }
608 }
609 else if( key!=ERR )
610 {
611 switch(wch)
612 {
613 case KEY_CTRL_A:
614 x=0;
615 cursor=0;
616 start=0;
617 break;
618 case KEY_CTRL_C:
619 exit(EXIT_SUCCESS);
620 break;
621 case KEY_CTRL_D:
622 delete(&cursor, wline);
623 break;
624 case KEY_CTRL_E:
625 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
626 break;
627 case TAB:
628 if( gcmp )
629 {
630 char *prefix = NULL;
631 GList *list;
633 i = wcstombs(NULL,wline,0)+1;
634 mbline = g_malloc0(i);
635 wcstombs(mbline, wline, i);
637 if(wrln_pre_completion_callback)
638 wrln_pre_completion_callback(gcmp, mbline,
639 wrln_completion_callback_data);
640 list = g_completion_complete(gcmp, mbline, &prefix);
641 if( prefix )
642 {
643 mbstowcs(wline, prefix, wrln_max_line_size);
644 cursor_move_to_eol(&cursor, &start, width, x0, x1, wline);
645 g_free(prefix);
646 }
647 else
648 screen_bell();
649 if( wrln_post_completion_callback )
650 wrln_post_completion_callback(gcmp, mbline, list,
651 wrln_completion_callback_data);
653 g_free(mbline);
654 }
655 break;
656 case KEY_CTRL_G:
657 screen_bell();
658 g_free(wline);
659 if( history )
660 {
661 g_free(hcurrent->data);
662 hcurrent->data = NULL;
663 *history = g_list_delete_link(*history, hcurrent);
664 }
665 return NULL;
666 case KEY_CTRL_K:
667 wline[cursor] = 0;
668 break;
669 case KEY_CTRL_U:
670 cursor = wcslen(wline);
671 for (i = 0;i < cursor; i++)
672 wline[i] = '\0';
673 cursor = 0;
674 break;
675 case KEY_CTRL_Z:
676 sigstop();
677 break;
678 case 127:
679 backspace(&cursor, &start, width, x0, x1, wline);
680 break;
681 case '\n':
682 case 13:
683 /* ignore char */
684 break;
685 default:
686 if( (wcslen(wline+cursor)) )
687 {
688 /* the cursor is not at the last pos */
689 wchar_t *tmp = NULL;
690 gsize len = (wcslen(wline+cursor)+1);
691 tmp = g_malloc0(len*sizeof(wchar_t));
692 wmemcpy(tmp, wline+cursor, len);
693 wline[cursor] = wch;
694 wline[cursor+1] = 0;
695 wcscat(&wline[cursor+1], tmp);
696 g_free(tmp);
697 cursor_move_right(&cursor, &start, width, x0, x1, wline);
698 }
699 else
700 {
701 FILE *ff = fopen ("curspr", "a+");
702 fprintf (ff, "%i", cursor);
703 wline[cursor] = wch;
704 wline[cursor+1] = 0;
705 cursor_move_right(&cursor, &start, width, x0, x1, wline);
706 }
707 }
708 }
709 drawline(cursor, start, width, x0, y, masked, wline, w);
710 }
711 i = wcstombs(NULL,wline,0)+1;
712 mbline = g_malloc0(i);
713 wcstombs(mbline, wline, i);
715 /* update history */
716 if( history )
717 {
718 if( strlen(mbline) )
719 {
720 /* update the current history entry */
721 size_t size = strlen(mbline)+1;
722 hcurrent->data = g_realloc(hcurrent->data, size);
723 g_strlcpy(hcurrent->data, mbline, size);
724 }
725 else
726 {
727 /* the line was empty - remove the current history entry */
728 g_free(hcurrent->data);
729 hcurrent->data = NULL;
730 *history = g_list_delete_link(*history, hcurrent);
731 }
733 while( g_list_length(*history) > wrln_max_history_length )
734 {
735 GList *first = g_list_first(*history);
737 /* remove the oldest history entry */
738 g_free(first->data);
739 first->data = NULL;
740 *history = g_list_delete_link(*history, first);
741 }
742 }
743 return mbline;
744 }
746 #endif
748 gchar *
749 wreadln(WINDOW *w,
750 const gchar *prompt,
751 const gchar *initial_value,
752 gint x1,
753 GList **history,
754 GCompletion *gcmp)
755 {
756 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
757 }
759 gchar *
760 wreadln_masked(WINDOW *w,
761 const gchar *prompt,
762 const gchar *initial_value,
763 gint x1,
764 GList **history,
765 GCompletion *gcmp)
766 {
767 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
768 }