Code

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