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