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 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
52 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
54 extern void screen_bell(void);
55 extern size_t my_strlen(char *str);
58 #ifndef USE_NCURSESW
59 /* libcurses version */
61 gchar *
62 wreadln(WINDOW *w,
63 gchar *prompt,
64 gchar *initial_value,
65 gint x1,
66 GList **history,
67 GCompletion *gcmp)
68 {
69 GList *hlist = NULL, *hcurrent = NULL;
70 gchar *line;
71 gint x0, y, width;
72 gint cursor = 0, start = 0;
73 gint key = 0, i;
75 /* move the cursor one step to the right */
76 void cursor_move_right(void) {
77 if( cursor < my_strlen(line) && cursor<wrln_max_line_size-1 )
78 {
79 cursor++;
80 if( cursor+x0 >= x1 && start<cursor-width+1)
81 start++;
82 }
83 }
84 /* move the cursor one step to the left */
85 void cursor_move_left(void) {
86 if( cursor > 0 )
87 {
88 if( cursor==start && start > 0 )
89 start--;
90 cursor--;
91 }
92 }
93 /* move the cursor to the end of the line */
94 void cursor_move_to_eol(void) {
95 cursor = my_strlen(line);
96 if( cursor+x0 >= x1 )
97 start = cursor-width+1;
98 }
99 /* draw line buffer and update cursor position */
100 void drawline() {
101 wmove(w, y, x0);
102 /* clear input area */
103 whline(w, ' ', width);
104 /* print visible part of the line buffer */
105 waddnstr(w, line+start, width);
106 /* move the cursor to the correct position */
107 wmove(w, y, x0 + cursor-start);
108 /* tell ncurses to redraw the screen */
109 doupdate();
110 }
113 /* allocate a line buffer */
114 line = g_malloc0(wrln_max_line_size);
115 /* turn off echo */
116 noecho();
117 /* make shure the cursor is visible */
118 curs_set(1);
119 /* print prompt string */
120 if( prompt )
121 waddstr(w, prompt);
122 /* retrive y and x0 position */
123 getyx(w, y, x0);
124 /* check the x1 value */
125 if( x1<=x0 || x1>COLS )
126 x1 = COLS;
127 width = x1-x0;
128 /* clear input area */
129 mvwhline(w, y, x0, ' ', width);
131 if( history )
132 {
133 /* append the a new line to our history list */
134 *history = g_list_append(*history, g_malloc0(wrln_max_line_size));
135 /* hlist points to the current item in the history list */
136 hlist = g_list_last(*history);
137 hcurrent = hlist;
138 }
140 if( initial_value == (char *) -1 )
141 {
142 /* get previous history entry */
143 if( history && hlist->prev )
144 {
145 if( hlist==hcurrent )
146 {
147 /* save the current line */
148 g_strlcpy(hlist->data, line, wrln_max_line_size);
149 }
150 /* get previous line */
151 hlist = hlist->prev;
152 g_strlcpy(line, hlist->data, wrln_max_line_size);
153 }
154 cursor_move_to_eol();
155 drawline();
156 }
157 else if( initial_value )
158 {
159 /* copy the initial value to the line buffer */
160 g_strlcpy(line, initial_value, wrln_max_line_size);
161 cursor_move_to_eol();
162 drawline();
163 }
165 while( key!=13 && key!='\n' )
166 {
167 if( wrln_wgetch )
168 key = wrln_wgetch(w);
169 else
170 key = wgetch(w);
172 /* check if key is a function key */
173 for(i=0; i<63; i++)
174 if( key==KEY_F(i) )
175 {
176 key=KEY_F(1);
177 i=64;
178 }
180 switch (key)
181 {
182 #ifdef HAVE_GETMOUSE
183 case KEY_MOUSE: /* ignore mouse events */
184 #endif
185 case ERR: /* ingnore errors */
186 break;
188 case KEY_RESIZE:
189 /* a resize event */
190 if( x1>COLS )
191 {
192 x1=COLS;
193 width = x1-x0;
194 cursor_move_to_eol();
195 }
196 /* make shure the cursor is visible */
197 curs_set(1);
198 break;
200 case TAB:
201 if( gcmp )
202 {
203 char *prefix = NULL;
204 GList *list;
206 if(wrln_pre_completion_callback)
207 wrln_pre_completion_callback(gcmp, line);
208 list = g_completion_complete(gcmp, line, &prefix);
209 if( prefix )
210 {
211 g_strlcpy(line, prefix, wrln_max_line_size);
212 cursor_move_to_eol();
213 g_free(prefix);
214 }
215 else
216 screen_bell();
217 if( wrln_post_completion_callback )
218 wrln_post_completion_callback(gcmp, line, list);
219 }
220 break;
222 case KEY_CTRL_G:
223 screen_bell();
224 g_free(line);
225 if( history )
226 {
227 g_free(hcurrent->data);
228 hcurrent->data = NULL;
229 *history = g_list_delete_link(*history, hcurrent);
230 }
231 return NULL;
233 case KEY_LEFT:
234 cursor_move_left();
235 break;
236 case KEY_RIGHT:
237 cursor_move_right();
238 break;
239 case KEY_HOME:
240 case KEY_CTRL_A:
241 cursor = 0;
242 start = 0;
243 break;
244 case KEY_END:
245 case KEY_CTRL_E:
246 cursor_move_to_eol();
247 break;
248 case KEY_CTRL_K:
249 line[cursor] = 0;
250 break;
251 case 127:
252 case KEY_BCKSPC: /* handle backspace: copy all */
253 case KEY_BACKSPACE: /* chars starting from curpos */
254 if( cursor > 0 ) /* - 1 from buf[n+1] to buf */
255 {
256 for (i = cursor - 1; line[i] != 0; i++)
257 line[i] = line[i + 1];
258 cursor_move_left();
259 }
260 break;
261 case KEY_DC: /* handle delete key. As above */
262 case KEY_CTRL_D:
263 if( cursor <= my_strlen(line) - 1 )
264 {
265 for (i = cursor; line[i] != 0; i++)
266 line[i] = line[i + 1];
267 }
268 break;
269 case KEY_UP:
270 /* get previous history entry */
271 if( history && hlist->prev )
272 {
273 if( hlist==hcurrent )
274 {
275 /* save the current line */
276 g_strlcpy(hlist->data, line, wrln_max_line_size);
277 }
278 /* get previous line */
279 hlist = hlist->prev;
280 g_strlcpy(line, hlist->data, wrln_max_line_size);
281 }
282 cursor_move_to_eol();
283 break;
284 case KEY_DOWN:
285 /* get next history entry */
286 if( history && hlist->next )
287 {
288 /* get next line */
289 hlist = hlist->next;
290 g_strlcpy(line, hlist->data, wrln_max_line_size);
291 }
292 cursor_move_to_eol();
293 break;
295 case '\n':
296 case 13:
297 case KEY_IC:
298 case KEY_PPAGE:
299 case KEY_NPAGE:
300 case KEY_F(1):
301 /* ignore char */
302 break;
303 default:
304 if (key >= 32)
305 {
306 if (strlen (line + cursor)) /* if the cursor is */
307 { /* not at the last pos */
308 gchar *tmp = 0;
309 gsize size = strlen(line + cursor) + 1;
311 tmp = g_malloc0(size);
312 g_strlcpy (tmp, line + cursor, size);
313 line[cursor] = key;
314 line[cursor + 1] = 0;
315 g_strlcat (&line[cursor + 1], tmp, size);
316 g_free(tmp);
317 cursor_move_right();
318 }
319 else
320 {
321 line[cursor + 1] = 0;
322 line[cursor] = key;
323 cursor_move_right();
324 }
325 }
326 }
328 drawline();
329 }
331 /* update history */
332 if( history )
333 {
334 if( strlen(line) )
335 {
336 /* update the current history entry */
337 size_t size = strlen(line)+1;
338 hcurrent->data = g_realloc(hcurrent->data, size);
339 g_strlcpy(hcurrent->data, line, size);
340 }
341 else
342 {
343 /* the line was empty - remove the current history entry */
344 g_free(hcurrent->data);
345 hcurrent->data = NULL;
346 *history = g_list_delete_link(*history, hcurrent);
347 }
349 while( g_list_length(*history) > wrln_max_history_length )
350 {
351 GList *first = g_list_first(*history);
353 /* remove the oldest history entry */
354 g_free(first->data);
355 first->data = NULL;
356 *history = g_list_delete_link(*history, first);
357 }
358 }
360 return g_realloc(line, strlen(line)+1);
361 }
363 #else
364 /* libcursesw version */
366 gchar *
367 wreadln(WINDOW *w,
368 gchar *prompt,
369 gchar *initial_value,
370 gint x1,
371 GList **history,
372 GCompletion *gcmp)
373 {
374 GList *hlist = NULL, *hcurrent = NULL;
375 wchar_t *wline;
376 gchar *mbline;
377 gint x0, x, y, width, start;
378 gint cursor;
379 wint_t wch;
380 gint key;
381 gint i;
383 /* move the cursor to the beginning of the line */
384 void cursor_move_home(void) {
385 x=0;
386 cursor=0;
387 start=0;
388 }
389 /* move the cursor to the end of the line */
390 void cursor_move_to_eol(void) {
391 cursor = wcslen(wline);
392 //x=wcswidth(wline,cursor);
393 if( cursor+x0 >= x1 )
394 start = cursor-width+1;
395 }
396 /* move the cursor one step to the left */
397 void cursor_move_left(void) {
398 if( cursor > 0 )
399 {
400 if( cursor==start && start > 0 )
401 start--;
402 //x-=wcwidth(wline[cursor]);
403 cursor--;
404 }
405 }
406 /* move the cursor one step to the right */
407 void cursor_move_right(void) {
408 if( cursor < wcslen(wline) && cursor<wrln_max_line_size-1 )
409 {
410 //x +=wcwidth(wline[cursor]);
411 cursor++;
412 if( cursor+x0 >= x1 && start<cursor-width+1)
413 start++;
414 }
415 }
416 /* handle backspace */
417 void backspace() {
418 if( cursor > 0 )
419 {
420 for (i = cursor - 1; wline[i] != 0; i++)
421 wline[i] = wline[i + 1];
422 cursor_move_left();
423 }
424 }
425 /* handle delete */
426 void delete() {
427 if( cursor <= wcslen(wline) - 1 )
428 {
429 for (i = cursor; wline[i] != 0; i++)
430 wline[i] = wline[i + 1];
431 }
432 }
433 /* draw line buffer and update cursor position */
434 void drawline() {
435 wmove(w, y, x0);
436 /* clear input area */
437 whline(w, ' ', width);
438 /* print visible part of the line buffer */
439 waddnwstr(w, wline+start, width);
440 /* move the cursor to the correct position */
441 wmove(w, y, x0 + cursor-start);
442 /* tell ncurses to redraw the screen */
443 doupdate();
444 }
446 /* initialize variables */
447 start = 0;
448 x = 0;
449 cursor = 0;
450 mbline = NULL;
452 /* allocate a line buffer */
453 wline = g_malloc0(wrln_max_line_size*sizeof(wchar_t));
454 /* turn off echo */
455 noecho();
456 /* make shure the cursor is visible */
457 curs_set(1);
458 /* print prompt string */
459 if( prompt )
460 waddstr(w, prompt);
461 /* retrive y and x0 position */
462 getyx(w, y, x0);
463 /* check the x1 value */
464 if( x1<=x0 || x1>COLS )
465 x1 = COLS;
466 width = x1-x0;
467 /* clear input area */
468 mvwhline(w, y, x0, ' ', width);
471 if( history )
472 {
473 /* append the a new line to our history list */
474 *history = g_list_append(*history, g_malloc0(wrln_max_line_size));
475 /* hlist points to the current item in the history list */
476 hlist = g_list_last(*history);
477 hcurrent = hlist;
478 }
479 if( initial_value == (char *) -1 )
480 {
481 /* get previous history entry */
482 if( history && hlist->prev )
483 {
484 if( hlist==hcurrent )
485 {
486 /* save the current line */
487 //g_strlcpy(hlist->data, line, wrln_max_line_size);
488 }
489 /* get previous line */
490 hlist = hlist->prev;
491 mbstowcs(wline, hlist->data, wrln_max_line_size);
492 }
493 cursor_move_to_eol();
494 drawline();
495 }
496 else if( initial_value )
497 {
498 /* copy the initial value to the line buffer */
499 mbstowcs(wline, initial_value, wrln_max_line_size);
500 cursor_move_to_eol();
501 drawline();
502 }
504 wch=0;
505 key=0;
506 while( wch!=13 && wch!='\n' )
507 {
508 key = wget_wch(w, &wch);
510 if( key==KEY_CODE_YES )
511 {
512 /* function key */
513 switch(wch)
514 {
515 case KEY_HOME:
516 cursor_move_home();
517 break;
518 case KEY_END:
519 cursor_move_to_eol();
520 break;
521 case KEY_LEFT:
522 cursor_move_left();
523 break;
524 case KEY_RIGHT:
525 cursor_move_right();
526 break;
527 case KEY_DC:
528 delete();
529 break;
530 case KEY_BCKSPC:
531 case KEY_BACKSPACE:
532 backspace();
533 break;
534 case KEY_UP:
535 /* get previous history entry */
536 if( history && hlist->prev )
537 {
538 if( hlist==hcurrent )
539 {
540 /* save the current line */
541 wcstombs(hlist->data, wline, wrln_max_line_size);
542 }
543 /* get previous line */
544 hlist = hlist->prev;
545 mbstowcs(wline, hlist->data, wrln_max_line_size);
546 }
547 cursor_move_to_eol();
548 break;
549 case KEY_DOWN:
550 /* get next history entry */
551 if( history && hlist->next )
552 {
553 /* get next line */
554 hlist = hlist->next;
555 mbstowcs(wline, hlist->data, wrln_max_line_size);
556 }
557 cursor_move_to_eol();
558 break;
559 case KEY_RESIZE:
560 /* resize event */
561 if( x1>COLS )
562 {
563 x1=COLS;
564 width = x1-x0;
565 cursor_move_to_eol();
566 }
567 /* make shure the cursor is visible */
568 curs_set(1);
569 break;
570 }
572 }
573 else if( key!=ERR )
574 {
575 switch(wch)
576 {
577 case KEY_CTRL_A:
578 cursor_move_home();
579 break;
580 case KEY_CTRL_C:
581 exit(EXIT_SUCCESS);
582 break;
583 case KEY_CTRL_D:
584 delete();
585 break;
586 case KEY_CTRL_E:
587 cursor_move_to_eol();
588 break;
589 case TAB:
590 if( gcmp )
591 {
592 char *prefix = NULL;
593 GList *list;
595 i = wcstombs(NULL,wline,0)+1;
596 mbline = g_malloc0(i);
597 wcstombs(mbline, wline, i);
599 if(wrln_pre_completion_callback)
600 wrln_pre_completion_callback(gcmp, mbline);
601 list = g_completion_complete(gcmp, mbline, &prefix);
602 if( prefix )
603 {
604 mbstowcs(wline, prefix, wrln_max_line_size);
605 cursor_move_to_eol();
606 g_free(prefix);
607 }
608 else
609 screen_bell();
610 if( wrln_post_completion_callback )
611 wrln_post_completion_callback(gcmp, mbline, list);
613 g_free(mbline);
614 }
615 break;
616 case KEY_CTRL_G:
617 screen_bell();
618 g_free(wline);
619 if( history )
620 {
621 g_free(hcurrent->data);
622 hcurrent->data = NULL;
623 *history = g_list_delete_link(*history, hcurrent);
624 }
625 return NULL;
626 case KEY_CTRL_K:
627 wline[cursor] = 0;
628 break;
629 case KEY_CTRL_Z:
630 sigstop();
631 break;
632 case 127:
633 backspace();
634 break;
635 case '\n':
636 case 13:
637 /* ignore char */
638 break;
639 default:
640 if( (wcslen(wline+cursor)) )
641 {
642 /* the cursor is not at the last pos */
643 wchar_t *tmp = NULL;
644 gsize len = (wcslen(wline+cursor)+1);
645 tmp = g_malloc0(len*sizeof(wchar_t));
646 wmemcpy(tmp, wline+cursor, len);
647 wline[cursor] = wch;
648 wline[cursor+1] = 0;
649 wcscat(&wline[cursor+1], tmp);
650 g_free(tmp);
651 cursor_move_right();
652 }
653 else
654 {
655 wline[cursor] = wch;
656 wline[cursor+1] = 0;
657 cursor_move_right();
658 }
659 }
660 }
661 drawline();
662 }
664 i = wcstombs(NULL,wline,0)+1;
665 mbline = g_malloc0(i);
666 wcstombs(mbline, wline, i);
668 /* update history */
669 if( history )
670 {
671 if( strlen(mbline) )
672 {
673 /* update the current history entry */
674 size_t size = strlen(mbline)+1;
675 hcurrent->data = g_realloc(hcurrent->data, size);
676 g_strlcpy(hcurrent->data, mbline, size);
677 }
678 else
679 {
680 /* the line was empty - remove the current history entry */
681 g_free(hcurrent->data);
682 hcurrent->data = NULL;
683 *history = g_list_delete_link(*history, hcurrent);
684 }
686 while( g_list_length(*history) > wrln_max_history_length )
687 {
688 GList *first = g_list_first(*history);
690 /* remove the oldest history entry */
691 g_free(first->data);
692 first->data = NULL;
693 *history = g_list_delete_link(*history, first);
694 }
695 }
696 return mbline;
697 }
699 #endif