Code

Merge remote branches 'avuton/master' and 'jn/dev'
[ncmpc.git] / src / wreadln.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2010 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "wreadln.h"
21 #include "charset.h"
22 #include "screen_utils.h"
23 #include "config.h"
25 #include <assert.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <glib.h>
30 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(WIN32)
31 #include <sys/poll.h>
32 #endif
34 #define KEY_CTRL_A   1
35 #define KEY_CTRL_B   2
36 #define KEY_CTRL_C   3
37 #define KEY_CTRL_D   4
38 #define KEY_CTRL_E   5
39 #define KEY_CTRL_F   6
40 #define KEY_CTRL_G   7
41 #define KEY_CTRL_K   11
42 #define KEY_CTRL_N   14
43 #define KEY_CTRL_P   16
44 #define KEY_CTRL_U   21
45 #define KEY_CTRL_W   23
46 #define KEY_CTRL_Z   26
47 #define KEY_BCKSPC   8
48 #define TAB          9
50 struct wreadln {
51         /** the ncurses window where this field is displayed */
52         WINDOW *const w;
54         /** the origin coordinates in the window */
55         unsigned x, y;
57         /** the screen width of the input field */
58         unsigned width;
60         /** is the input masked, i.e. characters displayed as '*'? */
61         const gboolean masked;
63         /** the byte position of the cursor */
64         size_t cursor;
66         /** the byte position displayed at the origin (for horizontal
67             scrolling) */
68         size_t start;
70         /** the current value */
71         gchar line[1024];
72 };
74 /** max items stored in the history list */
75 static const guint wrln_max_history_length = 32;
77 #ifndef NCMPC_MINI
78 void *wrln_completion_callback_data = NULL;
79 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
80 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
81 #endif
83 /** converts a byte position to a screen column */
84 static unsigned
85 byte_to_screen(const gchar *data, size_t x)
86 {
87 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
88         gchar *dup;
89         char *p;
90         unsigned width;
92         assert(x <= strlen(data));
94         dup = g_strdup(data);
95         dup[x] = 0;
96         p = replace_locale_to_utf8(dup);
98         width = utf8_width(p);
99         g_free(p);
101         return width;
102 #else
103         (void)data;
105         return (unsigned)x;
106 #endif
109 /** finds the first character which doesn't fit on the screen */
110 static size_t
111 screen_to_bytes(const gchar *data, unsigned width)
113 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
114         size_t length = strlen(data);
115         gchar *dup = g_strdup(data);
116         char *p;
117         unsigned p_width;
119         while (true) {
120                 dup[length] = 0;
121                 p = locale_to_utf8(dup);
122                 p_width = utf8_width(p);
123                 g_free(p);
124                 if (p_width <= width)
125                         break;
127                 --length;
128         }
130         g_free(dup);
132         return length;
133 #else
134         (void)data;
136         return (size_t)width;
137 #endif
140 /** returns the screen column where the cursor is located */
141 static unsigned
142 cursor_column(const struct wreadln *wr)
144         return byte_to_screen(wr->line + wr->start,
145                               wr->cursor - wr->start);
148 /** returns the offset in the string to align it at the right border
149     of the screen */
150 static inline size_t
151 right_align_bytes(const gchar *data, size_t right, unsigned width)
153 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
154         gchar *dup;
155         size_t start = 0;
157         assert(right <= strlen(data));
159         dup = g_strdup(data);
160         dup[right] = 0;
162         while (dup[start] != 0) {
163                 char *p = locale_to_utf8(dup + start), *q;
164                 unsigned p_width = utf8_width(p);
165                 gunichar c;
167                 if (p_width < width) {
168                         g_free(p);
169                         break;
170                 }
172                 c = g_utf8_get_char(p);
173                 p[g_unichar_to_utf8(c, NULL)] = 0;
174                 q = utf8_to_locale(p);
175                 g_free(p);
177                 start += strlen(q);
178                 g_free(q);
179         }
181         g_free(dup);
183         return start;
184 #else
185         (void)data;
187         return right >= width ? right + 1 - width : 0;
188 #endif
191 /** returns the size (in bytes) of the next character */
192 static inline size_t
193 next_char_size(const gchar *data)
195 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
196         char *p = locale_to_utf8(data), *q;
197         gunichar c;
198         size_t size;
200         c = g_utf8_get_char(p);
201         p[g_unichar_to_utf8(c, NULL)] = 0;
202         q = utf8_to_locale(p);
203         g_free(p);
205         size = strlen(q);
206         g_free(q);
208         return size;
209 #else
210         (void)data;
212         return 1;
213 #endif
216 /** returns the size (in bytes) of the previous character */
217 static inline size_t
218 prev_char_size(const gchar *data, size_t x)
220 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
221         char *p = locale_to_utf8(data), *q;
222         gunichar c;
223         size_t size;
225         assert(x > 0);
227         q = p;
228         while (true) {
229                 c = g_utf8_get_char(q);
230                 size = g_unichar_to_utf8(c, NULL);
231                 if (size > x)
232                         size = x;
233                 x -= size;
234                 if (x == 0) {
235                         g_free(p);
236                         return size;
237                 }
239                 q += size;
240         }
241 #else
242         (void)data;
243         (void)x;
245         return 1;
246 #endif
249 /* move the cursor one step to the right */
250 static inline void cursor_move_right(struct wreadln *wr)
252         size_t size;
254         if (wr->line[wr->cursor] == 0)
255                 return;
257         size = next_char_size(wr->line + wr->cursor);
258         wr->cursor += size;
259         if (cursor_column(wr) >= wr->width)
260                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
263 /* move the cursor one step to the left */
264 static inline void cursor_move_left(struct wreadln *wr)
266         size_t size;
268         if (wr->cursor == 0)
269                 return;
271         size = prev_char_size(wr->line, wr->cursor);
272         assert(wr->cursor >= size);
273         wr->cursor -= size;
274         if (wr->cursor < wr->start)
275                 wr->start = wr->cursor;
278 /* move the cursor to the end of the line */
279 static inline void cursor_move_to_eol(struct wreadln *wr)
281         wr->cursor = strlen(wr->line);
282         if (cursor_column(wr) >= wr->width)
283                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
286 /* draw line buffer and update cursor position */
287 static inline void drawline(const struct wreadln *wr)
289         wmove(wr->w, wr->y, wr->x);
290         /* clear input area */
291         whline(wr->w, ' ', wr->width);
292         /* print visible part of the line buffer */
293         if (wr->masked)
294                 whline(wr->w, '*', utf8_width(wr->line + wr->start));
295         else
296                 waddnstr(wr->w, wr->line + wr->start,
297                          screen_to_bytes(wr->line, wr->width));
298         /* move the cursor to the correct position */
299         wmove(wr->w, wr->y, wr->x + cursor_column(wr));
300         /* tell ncurses to redraw the screen */
301         doupdate();
304 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
305 static bool
306 multibyte_is_complete(const char *p, size_t length)
308         GError *error = NULL;
309         gchar *q = g_locale_to_utf8(p, length,
310                                     NULL, NULL, &error);
311         if (q != NULL) {
312                 g_free(q);
313                 return true;
314         } else {
315                 g_error_free(error);
316                 return false;
317         }
319 #endif
321 static void
322 wreadln_insert_byte(struct wreadln *wr, gint key)
324         size_t rest = strlen(wr->line + wr->cursor) + 1;
325 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined (WIN32)
326         char buffer[32] = { key };
327         size_t length = 1;
328         struct pollfd pfd = {
329                 .fd = 0,
330                 .events = POLLIN,
331         };
332         int ret;
334         /* wide version: try to complete the multibyte sequence */
336         while (length < sizeof(buffer)) {
337                 if (multibyte_is_complete(buffer, length))
338                         /* sequence is complete */
339                         break;
341                 /* poll for more bytes on stdin, without timeout */
343                 ret = poll(&pfd, 1, 0);
344                 if (ret <= 0)
345                         /* no more input from keyboard */
346                         break;
348                 buffer[length++] = wgetch(wr->w);
349         }
351         memmove(wr->line + wr->cursor + length,
352                 wr->line + wr->cursor, rest);
353         memcpy(wr->line + wr->cursor, buffer, length);
355 #else
356         const size_t length = 1;
358         memmove(wr->line + wr->cursor + length,
359                 wr->line + wr->cursor, rest);
360         wr->line[wr->cursor] = key;
362 #endif
364         wr->cursor += length;
365         if (cursor_column(wr) >= wr->width)
366                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
369 static void
370 wreadln_delete_char(struct wreadln *wr, size_t x)
372         size_t rest, length;
374         assert(x < strlen(wr->line));
376         length = next_char_size(&wr->line[x]);
377         rest = strlen(&wr->line[x + length]) + 1;
378         memmove(&wr->line[x], &wr->line[x + length], rest);
381 /* libcurses version */
383 static gchar *
384 _wreadln(WINDOW *w,
385          const gchar *prompt,
386          const gchar *initial_value,
387          unsigned x1,
388          GList **history,
389          GCompletion *gcmp,
390          gboolean masked)
392         struct wreadln wr = {
393                 .w = w,
394                 .masked = masked,
395                 .cursor = 0,
396                 .start = 0,
397         };
398         GList *hlist = NULL, *hcurrent = NULL;
399         gint key = 0;
400         size_t i;
402 #ifdef NCMPC_MINI
403         (void)gcmp;
404 #endif
406         /* turn off echo */
407         noecho();
408         /* make sure the cursor is visible */
409         curs_set(1);
410         /* print prompt string */
411         if (prompt) {
412                 waddstr(w, prompt);
413                 waddstr(w, ": ");
414         }
415         /* retrieve y and x0 position */
416         getyx(w, wr.y, wr.x);
417         /* check the x1 value */
418         if (x1 <= wr.x || x1 > (unsigned)COLS)
419                 x1 = COLS;
420         wr.width = x1 - wr.x;
421         /* clear input area */
422         mvwhline(w, wr.y, wr.x, ' ', wr.width);
424         if (history) {
425                 /* append the a new line to our history list */
426                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
427                 /* hlist points to the current item in the history list */
428                 hlist = g_list_last(*history);
429                 hcurrent = hlist;
430         }
432         if (initial_value == (char *)-1) {
433                 /* get previous history entry */
434                 if (history && hlist->prev) {
435                         if (hlist == hcurrent)
436                                 /* save the current line */
437                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
439                         /* get previous line */
440                         hlist = hlist->prev;
441                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
442                 }
443                 cursor_move_to_eol(&wr);
444                 drawline(&wr);
445         } else if (initial_value) {
446                 /* copy the initial value to the line buffer */
447                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
448                 cursor_move_to_eol(&wr);
449                 drawline(&wr);
450         }
452         while (key != 13 && key != '\n') {
453                 key = wgetch(w);
455                 /* check if key is a function key */
456                 for (i = 0; i < 63; i++)
457                         if (key == (int)KEY_F(i)) {
458                                 key = KEY_F(1);
459                                 i = 64;
460                         }
462                 switch (key) {
463 #ifdef HAVE_GETMOUSE
464                 case KEY_MOUSE: /* ignore mouse events */
465 #endif
466                 case ERR: /* ignore errors */
467                         break;
469                 case TAB:
470 #ifndef NCMPC_MINI
471                         if (gcmp) {
472                                 char *prefix = NULL;
473                                 GList *list;
475                                 if (wrln_pre_completion_callback)
476                                         wrln_pre_completion_callback(gcmp, wr.line,
477                                                                      wrln_completion_callback_data);
478                                 list = g_completion_complete(gcmp, wr.line, &prefix);
479                                 if (prefix) {
480                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
481                                         cursor_move_to_eol(&wr);
482                                         g_free(prefix);
483                                 } else
484                                         screen_bell();
486                                 if (wrln_post_completion_callback)
487                                         wrln_post_completion_callback(gcmp, wr.line, list,
488                                                                       wrln_completion_callback_data);
489                         }
490 #endif
491                         break;
493                 case KEY_CTRL_G:
494                         screen_bell();
495                         if (history) {
496                                 g_free(hcurrent->data);
497                                 hcurrent->data = NULL;
498                                 *history = g_list_delete_link(*history, hcurrent);
499                         }
500                         return NULL;
502                 case KEY_LEFT:
503                 case KEY_CTRL_B:
504                         cursor_move_left(&wr);
505                         break;
506                 case KEY_RIGHT:
507                 case KEY_CTRL_F:
508                         cursor_move_right(&wr);
509                         break;
510                 case KEY_HOME:
511                 case KEY_CTRL_A:
512                         wr.cursor = 0;
513                         wr.start = 0;
514                         break;
515                 case KEY_END:
516                 case KEY_CTRL_E:
517                         cursor_move_to_eol(&wr);
518                         break;
519                 case KEY_CTRL_K:
520                         wr.line[wr.cursor] = 0;
521                         break;
522                 case KEY_CTRL_U:
523                         wr.cursor = utf8_width(wr.line);
524                         for (i = 0; i < wr.cursor; i++)
525                                 wr.line[i] = '\0';
526                         wr.cursor = 0;
527                         break;
528                 case KEY_CTRL_W:
529                         /* Firstly remove trailing spaces. */
530                         for (i = wr.cursor; i > 0 && wr.line[i-1] == ' '; i--)
531                         {
532                                 cursor_move_left(&wr);
533                                 wreadln_delete_char(&wr, wr.cursor);
534                         }
535                         /* Then remove word until next space. */
536                         for (; i > 0 && wr.line[i-1] != ' '; i--)
537                         {
538                                 cursor_move_left(&wr);
539                                 wreadln_delete_char(&wr, wr.cursor);
540                         }
541                         break;
542                 case 127:
543                 case KEY_BCKSPC:        /* handle backspace: copy all */
544                 case KEY_BACKSPACE:     /* chars starting from curpos */
545                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
546                                 cursor_move_left(&wr);
547                                 wreadln_delete_char(&wr, wr.cursor);
548                         }
549                         break;
550                 case KEY_DC:            /* handle delete key. As above */
551                 case KEY_CTRL_D:
552                         if (wr.line[wr.cursor] != 0)
553                                 wreadln_delete_char(&wr, wr.cursor);
554                         break;
555                 case KEY_UP:
556                 case KEY_CTRL_P:
557                         /* get previous history entry */
558                         if (history && hlist->prev) {
559                                 if (hlist == hcurrent)
560                                         /* save the current line */
561                                         g_strlcpy(hlist->data, wr.line,
562                                                   sizeof(wr.line));
564                                 /* get previous line */
565                                 hlist = hlist->prev;
566                                 g_strlcpy(wr.line, hlist->data,
567                                           sizeof(wr.line));
568                         }
569                         cursor_move_to_eol(&wr);
570                         break;
571                 case KEY_DOWN:
572                 case KEY_CTRL_N:
573                         /* get next history entry */
574                         if (history && hlist->next) {
575                                 /* get next line */
576                                 hlist = hlist->next;
577                                 g_strlcpy(wr.line, hlist->data,
578                                           sizeof(wr.line));
579                         }
580                         cursor_move_to_eol(&wr);
581                         break;
583                 case '\n':
584                 case 13:
585                 case KEY_IC:
586                 case KEY_PPAGE:
587                 case KEY_NPAGE:
588                 case KEY_F(1):
589                         /* ignore char */
590                         break;
591                 default:
592                         if (key >= 32)
593                                 wreadln_insert_byte(&wr, key);
594                 }
596                 drawline(&wr);
597         }
599         /* update history */
600         if (history) {
601                 if (strlen(wr.line)) {
602                         /* update the current history entry */
603                         size_t size = strlen(wr.line) + 1;
604                         hcurrent->data = g_realloc(hcurrent->data, size);
605                         g_strlcpy(hcurrent->data, wr.line, size);
606                 } else {
607                         /* the line was empty - remove the current history entry */
608                         g_free(hcurrent->data);
609                         hcurrent->data = NULL;
610                         *history = g_list_delete_link(*history, hcurrent);
611                 }
613                 while (g_list_length(*history) > wrln_max_history_length) {
614                         GList *first = g_list_first(*history);
616                         /* remove the oldest history entry  */
617                         g_free(first->data);
618                         first->data = NULL;
619                         *history = g_list_delete_link(*history, first);
620                 }
621         }
623         if (wr.line[0] == 0)
624                 return NULL;
626         return g_strdup(wr.line);
629 gchar *
630 wreadln(WINDOW *w,
631         const gchar *prompt,
632         const gchar *initial_value,
633         unsigned x1,
634         GList **history,
635         GCompletion *gcmp)
637         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
640 gchar *
641 wreadln_masked(WINDOW *w,
642                const gchar *prompt,
643                const gchar *initial_value,
644                unsigned x1,
645                GList **history,
646                GCompletion *gcmp)
648         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);