Code

4dde082aa2948e78f9ef4f38619188c2f940cfe8
[ncmpc.git] / src / wreadln.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2017 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
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.
9  *
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.
14  *
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         assert(x <= strlen(data));
90         char *dup = g_strdup(data);
91         dup[x] = 0;
92         char *p = replace_locale_to_utf8(dup);
94         unsigned width = utf8_width(p);
95         g_free(p);
97         return width;
98 #else
99         (void)data;
101         return (unsigned)x;
102 #endif
105 /** finds the first character which doesn't fit on the screen */
106 static size_t
107 screen_to_bytes(const gchar *data, unsigned width)
109 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
110         size_t length = strlen(data);
111         gchar *dup = g_strdup(data);
113         while (true) {
114                 dup[length] = 0;
115                 char *p = locale_to_utf8(dup);
116                 unsigned p_width = utf8_width(p);
117                 g_free(p);
118                 if (p_width <= width)
119                         break;
121                 --length;
122         }
124         g_free(dup);
126         return length;
127 #else
128         (void)data;
130         return (size_t)width;
131 #endif
134 /** returns the screen column where the cursor is located */
135 static unsigned
136 cursor_column(const struct wreadln *wr)
138         return byte_to_screen(wr->line + wr->start,
139                               wr->cursor - wr->start);
142 /** returns the offset in the string to align it at the right border
143     of the screen */
144 static inline size_t
145 right_align_bytes(const gchar *data, size_t right, unsigned width)
147 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
148         gchar *dup;
149         size_t start = 0;
151         assert(right <= strlen(data));
153         dup = g_strdup(data);
154         dup[right] = 0;
156         while (dup[start] != 0) {
157                 char *p = locale_to_utf8(dup + start), *q;
158                 unsigned p_width = utf8_width(p);
160                 if (p_width < width) {
161                         g_free(p);
162                         break;
163                 }
165                 gunichar c = g_utf8_get_char(p);
166                 p[g_unichar_to_utf8(c, NULL)] = 0;
167                 q = utf8_to_locale(p);
168                 g_free(p);
170                 start += strlen(q);
171                 g_free(q);
172         }
174         g_free(dup);
176         return start;
177 #else
178         (void)data;
180         return right >= width ? right + 1 - width : 0;
181 #endif
184 /** returns the size (in bytes) of the next character */
185 static inline size_t
186 next_char_size(const gchar *data)
188 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
189         char *p = locale_to_utf8(data), *q;
191         gunichar c = g_utf8_get_char(p);
192         p[g_unichar_to_utf8(c, NULL)] = 0;
193         q = utf8_to_locale(p);
194         g_free(p);
196         size_t size = strlen(q);
197         g_free(q);
199         return size;
200 #else
201         (void)data;
203         return 1;
204 #endif
207 /** returns the size (in bytes) of the previous character */
208 static inline size_t
209 prev_char_size(const gchar *data, size_t x)
211 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
212         assert(x > 0);
214         char *p = locale_to_utf8(data);
216         char *q = p;
217         while (true) {
218                 gunichar c = g_utf8_get_char(q);
219                 size_t size = g_unichar_to_utf8(c, NULL);
220                 if (size > x)
221                         size = x;
222                 x -= size;
223                 if (x == 0) {
224                         g_free(p);
225                         return size;
226                 }
228                 q += size;
229         }
230 #else
231         (void)data;
232         (void)x;
234         return 1;
235 #endif
238 /* move the cursor one step to the right */
239 static inline void cursor_move_right(struct wreadln *wr)
241         if (wr->line[wr->cursor] == 0)
242                 return;
244         size_t size = next_char_size(wr->line + wr->cursor);
245         wr->cursor += size;
246         if (cursor_column(wr) >= wr->width)
247                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
250 /* move the cursor one step to the left */
251 static inline void cursor_move_left(struct wreadln *wr)
253         if (wr->cursor == 0)
254                 return;
256         size_t size = prev_char_size(wr->line, wr->cursor);
257         assert(wr->cursor >= size);
258         wr->cursor -= size;
259         if (wr->cursor < wr->start)
260                 wr->start = wr->cursor;
263 /* move the cursor to the end of the line */
264 static inline void cursor_move_to_eol(struct wreadln *wr)
266         wr->cursor = strlen(wr->line);
267         if (cursor_column(wr) >= wr->width)
268                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
271 /* draw line buffer and update cursor position */
272 static inline void drawline(const struct wreadln *wr)
274         wmove(wr->w, wr->y, wr->x);
275         /* clear input area */
276         whline(wr->w, ' ', wr->width);
277         /* print visible part of the line buffer */
278         if (wr->masked)
279                 whline(wr->w, '*', utf8_width(wr->line + wr->start));
280         else
281                 waddnstr(wr->w, wr->line + wr->start,
282                          screen_to_bytes(wr->line, wr->width));
283         /* move the cursor to the correct position */
284         wmove(wr->w, wr->y, wr->x + cursor_column(wr));
285         /* tell ncurses to redraw the screen */
286         doupdate();
289 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
290 static bool
291 multibyte_is_complete(const char *p, size_t length)
293         GError *error = NULL;
294         gchar *q = g_locale_to_utf8(p, length,
295                                     NULL, NULL, &error);
296         if (q != NULL) {
297                 g_free(q);
298                 return true;
299         } else {
300                 g_error_free(error);
301                 return false;
302         }
304 #endif
306 static void
307 wreadln_insert_byte(struct wreadln *wr, gint key)
309         size_t rest = strlen(wr->line + wr->cursor) + 1;
310 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined (WIN32)
311         char buffer[32] = { key };
312         size_t length = 1;
313         struct pollfd pfd = {
314                 .fd = 0,
315                 .events = POLLIN,
316         };
318         /* wide version: try to complete the multibyte sequence */
320         while (length < sizeof(buffer)) {
321                 if (multibyte_is_complete(buffer, length))
322                         /* sequence is complete */
323                         break;
325                 /* poll for more bytes on stdin, without timeout */
327                 if (poll(&pfd, 1, 0) <= 0)
328                         /* no more input from keyboard */
329                         break;
331                 buffer[length++] = wgetch(wr->w);
332         }
334         memmove(wr->line + wr->cursor + length,
335                 wr->line + wr->cursor, rest);
336         memcpy(wr->line + wr->cursor, buffer, length);
338 #else
339         const size_t length = 1;
341         memmove(wr->line + wr->cursor + length,
342                 wr->line + wr->cursor, rest);
343         wr->line[wr->cursor] = key;
345 #endif
347         wr->cursor += length;
348         if (cursor_column(wr) >= wr->width)
349                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
352 static void
353 wreadln_delete_char(struct wreadln *wr, size_t x)
355         assert(x < strlen(wr->line));
357         size_t length = next_char_size(&wr->line[x]);
358         size_t rest = strlen(&wr->line[x + length]) + 1;
359         memmove(&wr->line[x], &wr->line[x + length], rest);
362 /* libcurses version */
364 static gchar *
365 _wreadln(WINDOW *w,
366          const gchar *prompt,
367          const gchar *initial_value,
368          unsigned x1,
369          GList **history,
370          GCompletion *gcmp,
371          gboolean masked)
373         struct wreadln wr = {
374                 .w = w,
375                 .masked = masked,
376                 .cursor = 0,
377                 .start = 0,
378         };
379         GList *hlist = NULL, *hcurrent = NULL;
381 #ifdef NCMPC_MINI
382         (void)gcmp;
383 #endif
385         /* turn off echo */
386         noecho();
387         /* make sure the cursor is visible */
388         curs_set(1);
389         /* print prompt string */
390         if (prompt) {
391                 waddstr(w, prompt);
392                 waddstr(w, ": ");
393         }
394         /* retrieve y and x0 position */
395         getyx(w, wr.y, wr.x);
396         /* check the x1 value */
397         if (x1 <= wr.x || x1 > (unsigned)COLS)
398                 x1 = COLS;
399         wr.width = x1 - wr.x;
400         /* clear input area */
401         mvwhline(w, wr.y, wr.x, ' ', wr.width);
403         if (history) {
404                 /* append the a new line to our history list */
405                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
406                 /* hlist points to the current item in the history list */
407                 hlist = g_list_last(*history);
408                 hcurrent = hlist;
409         }
411         if (initial_value == (char *)-1) {
412                 /* get previous history entry */
413                 if (history && hlist->prev) {
414                         if (hlist == hcurrent)
415                                 /* save the current line */
416                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
418                         /* get previous line */
419                         hlist = hlist->prev;
420                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
421                 }
422                 cursor_move_to_eol(&wr);
423                 drawline(&wr);
424         } else if (initial_value) {
425                 /* copy the initial value to the line buffer */
426                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
427                 cursor_move_to_eol(&wr);
428                 drawline(&wr);
429         }
431         gint key = 0;
432         while (key != 13 && key != '\n') {
433                 key = wgetch(w);
435                 /* check if key is a function key */
436                 for (size_t i = 0; i < 63; i++)
437                         if (key == (int)KEY_F(i)) {
438                                 key = KEY_F(1);
439                                 i = 64;
440                         }
442                 switch (key) {
443                         size_t i;
445 #ifdef HAVE_GETMOUSE
446                 case KEY_MOUSE: /* ignore mouse events */
447 #endif
448                 case ERR: /* ignore errors */
449                         break;
451                 case TAB:
452 #ifndef NCMPC_MINI
453                         if (gcmp) {
454                                 char *prefix = NULL;
455                                 GList *list;
457                                 if (wrln_pre_completion_callback)
458                                         wrln_pre_completion_callback(gcmp, wr.line,
459                                                                      wrln_completion_callback_data);
460                                 list = g_completion_complete(gcmp, wr.line, &prefix);
461                                 if (prefix) {
462                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
463                                         cursor_move_to_eol(&wr);
464                                         g_free(prefix);
465                                 } else
466                                         screen_bell();
468                                 if (wrln_post_completion_callback)
469                                         wrln_post_completion_callback(gcmp, wr.line, list,
470                                                                       wrln_completion_callback_data);
471                         }
472 #endif
473                         break;
475                 case KEY_CTRL_G:
476                         screen_bell();
477                         if (history) {
478                                 g_free(hcurrent->data);
479                                 hcurrent->data = NULL;
480                                 *history = g_list_delete_link(*history, hcurrent);
481                         }
482                         return NULL;
484                 case KEY_LEFT:
485                 case KEY_CTRL_B:
486                         cursor_move_left(&wr);
487                         break;
488                 case KEY_RIGHT:
489                 case KEY_CTRL_F:
490                         cursor_move_right(&wr);
491                         break;
492                 case KEY_HOME:
493                 case KEY_CTRL_A:
494                         wr.cursor = 0;
495                         wr.start = 0;
496                         break;
497                 case KEY_END:
498                 case KEY_CTRL_E:
499                         cursor_move_to_eol(&wr);
500                         break;
501                 case KEY_CTRL_K:
502                         wr.line[wr.cursor] = 0;
503                         break;
504                 case KEY_CTRL_U:
505                         wr.cursor = utf8_width(wr.line);
506                         for (i = 0; i < wr.cursor; i++)
507                                 wr.line[i] = '\0';
508                         wr.cursor = 0;
509                         break;
510                 case KEY_CTRL_W:
511                         /* Firstly remove trailing spaces. */
512                         for (i = wr.cursor; i > 0 && wr.line[i-1] == ' '; i--)
513                         {
514                                 cursor_move_left(&wr);
515                                 wreadln_delete_char(&wr, wr.cursor);
516                         }
517                         /* Then remove word until next space. */
518                         for (; i > 0 && wr.line[i-1] != ' '; i--)
519                         {
520                                 cursor_move_left(&wr);
521                                 wreadln_delete_char(&wr, wr.cursor);
522                         }
523                         break;
524                 case 127:
525                 case KEY_BCKSPC:        /* handle backspace: copy all */
526                 case KEY_BACKSPACE:     /* chars starting from curpos */
527                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
528                                 cursor_move_left(&wr);
529                                 wreadln_delete_char(&wr, wr.cursor);
530                         }
531                         break;
532                 case KEY_DC:            /* handle delete key. As above */
533                 case KEY_CTRL_D:
534                         if (wr.line[wr.cursor] != 0)
535                                 wreadln_delete_char(&wr, wr.cursor);
536                         break;
537                 case KEY_UP:
538                 case KEY_CTRL_P:
539                         /* get previous history entry */
540                         if (history && hlist->prev) {
541                                 if (hlist == hcurrent)
542                                         /* save the current line */
543                                         g_strlcpy(hlist->data, wr.line,
544                                                   sizeof(wr.line));
546                                 /* get previous line */
547                                 hlist = hlist->prev;
548                                 g_strlcpy(wr.line, hlist->data,
549                                           sizeof(wr.line));
550                         }
551                         cursor_move_to_eol(&wr);
552                         break;
553                 case KEY_DOWN:
554                 case KEY_CTRL_N:
555                         /* get next history entry */
556                         if (history && hlist->next) {
557                                 /* get next line */
558                                 hlist = hlist->next;
559                                 g_strlcpy(wr.line, hlist->data,
560                                           sizeof(wr.line));
561                         }
562                         cursor_move_to_eol(&wr);
563                         break;
565                 case '\n':
566                 case 13:
567                 case KEY_IC:
568                 case KEY_PPAGE:
569                 case KEY_NPAGE:
570                 case KEY_F(1):
571                         /* ignore char */
572                         break;
573                 default:
574                         if (key >= 32)
575                                 wreadln_insert_byte(&wr, key);
576                 }
578                 drawline(&wr);
579         }
581         /* update history */
582         if (history) {
583                 if (strlen(wr.line)) {
584                         /* update the current history entry */
585                         size_t size = strlen(wr.line) + 1;
586                         hcurrent->data = g_realloc(hcurrent->data, size);
587                         g_strlcpy(hcurrent->data, wr.line, size);
588                 } else {
589                         /* the line was empty - remove the current history entry */
590                         g_free(hcurrent->data);
591                         hcurrent->data = NULL;
592                         *history = g_list_delete_link(*history, hcurrent);
593                 }
595                 unsigned history_length = g_list_length(*history);
596                 while (history_length > wrln_max_history_length) {
597                         GList *first = g_list_first(*history);
599                         /* remove the oldest history entry  */
600                         g_free(first->data);
601                         first->data = NULL;
602                         *history = g_list_delete_link(*history, first);
603                         --history_length;
604                 }
605         }
607         if (wr.line[0] == 0)
608                 return NULL;
610         return g_strdup(wr.line);
613 gchar *
614 wreadln(WINDOW *w,
615         const gchar *prompt,
616         const gchar *initial_value,
617         unsigned x1,
618         GList **history,
619         GCompletion *gcmp)
621         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
624 gchar *
625 wreadln_masked(WINDOW *w,
626                const gchar *prompt,
627                const gchar *initial_value,
628                unsigned x1,
629                GList **history,
630                GCompletion *gcmp)
632         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);