Code

bf31d1db349edbaadb6114f7400b8d8c1c7e2b87
[ncmpc.git] / src / wreadln.c
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
50  
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;
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)
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();
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)
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);
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)
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     }
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)
397   if( *cursor > 0 )
398     {
399       if( *cursor==*start && *start > 0 )
400         (*start)--;
401       (*cursor)--;
402     }
406 static inline void backspace(gint *cursor,
407                              gint *start,
408                              gint width,
409                              gint x0,
410                              gint x1,
411                              wchar_t *wline) 
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     }
422 /* handle delete */
423 static inline void delete(gint *cursor,
424                           wchar_t *wline) 
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     }
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)
442   *cursor = wcslen(line);
443   if( *cursor+x0 >= x1 )
444     *start = *cursor-width+1;
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)
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();
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)
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;
632                   
633                   i = wcstombs(NULL,wline,0)+1;
634                   mbline = g_malloc0(i);
635                   wcstombs(mbline, wline, i);
636                   
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);
652                   
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;
745  
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)
756         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
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)
767         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);