Code

fixed TAB completion support with libcursesw (path from René van Bevern)
[ncmpc.git] / src / wreadln.c
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
47  
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;
205               
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;
232           
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;
294           
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     }
359   
360   return g_realloc(line, strlen(line)+1);
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)
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;
594                   
595                   i = wcstombs(NULL,wline,0)+1;
596                   mbline = g_malloc0(i);
597                   wcstombs(mbline, wline, i);
598                   
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);
612                   
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;
698  
699 #endif