Code

mpdclient: don't update the status in mpdclient_cmd_crop()
[ncmpc.git] / src / wreadln.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 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(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
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(ENABLE_WIDE) || 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 = locale_to_utf8(dup);
97         g_free(dup);
99         width = utf8_width(p);
100         g_free(p);
102         return width;
103 #else
104         (void)data;
106         return (unsigned)x;
107 #endif
110 /** finds the first character which doesn't fit on the screen */
111 static size_t
112 screen_to_bytes(const gchar *data, unsigned width)
114 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
115         size_t length = strlen(data);
116         gchar *dup = g_strdup(data);
117         char *p;
118         unsigned p_width;
120         while (true) {
121                 dup[length] = 0;
122                 p = locale_to_utf8(dup);
123                 p_width = utf8_width(p);
124                 g_free(p);
125                 if (p_width <= width)
126                         break;
128                 --length;
129         }
131         g_free(dup);
133         return length;
134 #else
135         (void)data;
137         return (size_t)width;
138 #endif
141 /** returns the screen column where the cursor is located */
142 static unsigned
143 cursor_column(const struct wreadln *wr)
145         return byte_to_screen(wr->line + wr->start,
146                               wr->cursor - wr->start);
149 /** returns the offset in the string to align it at the right border
150     of the screen */
151 static inline size_t
152 right_align_bytes(const gchar *data, size_t right, unsigned width)
154 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
155         gchar *dup;
156         size_t start = 0;
158         assert(right <= strlen(data));
160         dup = g_strdup(data);
161         dup[right] = 0;
163         while (dup[start] != 0) {
164                 char *p = locale_to_utf8(dup + start), *q;
165                 unsigned p_width = utf8_width(p);
166                 gunichar c;
168                 if (p_width < width) {
169                         g_free(p);
170                         break;
171                 }
173                 c = g_utf8_get_char(p);
174                 p[g_unichar_to_utf8(c, NULL)] = 0;
175                 q = utf8_to_locale(p);
176                 g_free(p);
178                 start += strlen(q);
179                 g_free(q);
180         }
182         g_free(dup);
184         return start;
185 #else
186         (void)data;
188         return right >= width ? right + 1 - width : 0;
189 #endif
192 /** returns the size (in bytes) of the next character */
193 static inline size_t
194 next_char_size(const gchar *data)
196 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
197         char *p = locale_to_utf8(data), *q;
198         gunichar c;
199         size_t size;
201         c = g_utf8_get_char(p);
202         p[g_unichar_to_utf8(c, NULL)] = 0;
203         q = utf8_to_locale(p);
204         g_free(p);
206         size = strlen(q);
207         g_free(q);
209         return size;
210 #else
211         (void)data;
213         return 1;
214 #endif
217 /** returns the size (in bytes) of the previous character */
218 static inline size_t
219 prev_char_size(const gchar *data, size_t x)
221 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
222         char *p = locale_to_utf8(data), *q;
223         gunichar c;
224         size_t size;
226         assert(x > 0);
228         q = p;
229         while (true) {
230                 c = g_utf8_get_char(q);
231                 size = g_unichar_to_utf8(c, NULL);
232                 if (size > x)
233                         size = x;
234                 x -= size;
235                 if (x == 0) {
236                         g_free(p);
237                         return size;
238                 }
240                 q += size;
241         }
242 #else
243         (void)data;
244         (void)x;
246         return 1;
247 #endif
250 /* move the cursor one step to the right */
251 static inline void cursor_move_right(struct wreadln *wr)
253         size_t size;
255         if (wr->line[wr->cursor] == 0)
256                 return;
258         size = next_char_size(wr->line + wr->cursor);
259         wr->cursor += size;
260         if (cursor_column(wr) >= wr->width)
261                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
264 /* move the cursor one step to the left */
265 static inline void cursor_move_left(struct wreadln *wr)
267         size_t size;
269         if (wr->cursor == 0)
270                 return;
272         size = prev_char_size(wr->line, wr->cursor);
273         assert(wr->cursor >= size);
274         wr->cursor -= size;
275         if (wr->cursor < wr->start)
276                 wr->start = wr->cursor;
279 /* move the cursor to the end of the line */
280 static inline void cursor_move_to_eol(struct wreadln *wr)
282         wr->cursor = strlen(wr->line);
283         if (cursor_column(wr) >= wr->width)
284                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
287 /* draw line buffer and update cursor position */
288 static inline void drawline(const struct wreadln *wr)
290         wmove(wr->w, wr->y, wr->x);
291         /* clear input area */
292         whline(wr->w, ' ', wr->width);
293         /* print visible part of the line buffer */
294         if (wr->masked)
295                 whline(wr->w, '*', utf8_width(wr->line + wr->start));
296         else
297                 waddnstr(wr->w, wr->line + wr->start,
298                          screen_to_bytes(wr->line, wr->width));
299         /* move the cursor to the correct position */
300         wmove(wr->w, wr->y, wr->x + cursor_column(wr));
301         /* tell ncurses to redraw the screen */
302         doupdate();
305 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
306 static bool
307 multibyte_is_complete(const char *p, size_t length)
309         GError *error = NULL;
310         gchar *q = g_locale_to_utf8(p, length,
311                                     NULL, NULL, &error);
312         if (q != NULL) {
313                 g_free(q);
314                 return true;
315         } else {
316                 g_error_free(error);
317                 return false;
318         }
320 #endif
322 static void
323 wreadln_insert_byte(struct wreadln *wr, gint key)
325         size_t rest = strlen(wr->line + wr->cursor) + 1;
326 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
327         char buffer[32] = { key };
328         size_t length = 1;
329         struct pollfd pfd = {
330                 .fd = 0,
331                 .events = POLLIN,
332         };
333         int ret;
335         /* wide version: try to complete the multibyte sequence */
337         while (length < sizeof(buffer)) {
338                 if (multibyte_is_complete(buffer, length))
339                         /* sequence is complete */
340                         break;
342                 /* poll for more bytes on stdin, without timeout */
344                 ret = poll(&pfd, 1, 0);
345                 if (ret <= 0)
346                         /* no more input from keyboard */
347                         break;
349                 buffer[length++] = wgetch(wr->w);
350         }
352         memmove(wr->line + wr->cursor + length,
353                 wr->line + wr->cursor, rest);
354         memcpy(wr->line + wr->cursor, buffer, length);
356 #else
357         const size_t length = 1;
359         memmove(wr->line + wr->cursor + length,
360                 wr->line + wr->cursor, rest);
361         wr->line[wr->cursor] = key;
363 #endif
365         wr->cursor += length;
366         if (cursor_column(wr) >= wr->width)
367                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
370 static void
371 wreadln_delete_char(struct wreadln *wr, size_t x)
373         size_t rest, length;
375         assert(x < strlen(wr->line));
377         length = next_char_size(&wr->line[x]);
378         rest = strlen(&wr->line[x + length]) + 1;
379         memmove(&wr->line[x], &wr->line[x + length], rest);
382 /* libcurses version */
384 static gchar *
385 _wreadln(WINDOW *w,
386          const gchar *prompt,
387          const gchar *initial_value,
388          unsigned x1,
389          GList **history,
390          GCompletion *gcmp,
391          gboolean masked)
393         struct wreadln wr = {
394                 .w = w,
395                 .masked = masked,
396                 .cursor = 0,
397                 .start = 0,
398         };
399         GList *hlist = NULL, *hcurrent = NULL;
400         gint key = 0;
401         size_t i;
403 #ifdef NCMPC_MINI
404         (void)gcmp;
405 #endif
407         /* turn off echo */
408         noecho();
409         /* make sure the cursor is visible */
410         curs_set(1);
411         /* print prompt string */
412         if (prompt) {
413                 waddstr(w, prompt);
414                 waddstr(w, ": ");
415         }
416         /* retrieve y and x0 position */
417         getyx(w, wr.y, wr.x);
418         /* check the x1 value */
419         if (x1 <= wr.x || x1 > (unsigned)COLS)
420                 x1 = COLS;
421         wr.width = x1 - wr.x;
422         /* clear input area */
423         mvwhline(w, wr.y, wr.x, ' ', wr.width);
425         if (history) {
426                 /* append the a new line to our history list */
427                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
428                 /* hlist points to the current item in the history list */
429                 hlist = g_list_last(*history);
430                 hcurrent = hlist;
431         }
433         if (initial_value == (char *)-1) {
434                 /* get previous history entry */
435                 if (history && hlist->prev) {
436                         if (hlist == hcurrent)
437                                 /* save the current line */
438                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
440                         /* get previous line */
441                         hlist = hlist->prev;
442                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
443                 }
444                 cursor_move_to_eol(&wr);
445                 drawline(&wr);
446         } else if (initial_value) {
447                 /* copy the initial value to the line buffer */
448                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
449                 cursor_move_to_eol(&wr);
450                 drawline(&wr);
451         }
453         while (key != 13 && key != '\n') {
454                 key = wgetch(w);
456                 /* check if key is a function key */
457                 for (i = 0; i < 63; i++)
458                         if (key == (int)KEY_F(i)) {
459                                 key = KEY_F(1);
460                                 i = 64;
461                         }
463                 switch (key) {
464 #ifdef HAVE_GETMOUSE
465                 case KEY_MOUSE: /* ignore mouse events */
466 #endif
467                 case ERR: /* ignore errors */
468                         break;
470                 case TAB:
471 #ifndef NCMPC_MINI
472                         if (gcmp) {
473                                 char *prefix = NULL;
474                                 GList *list;
476                                 if (wrln_pre_completion_callback)
477                                         wrln_pre_completion_callback(gcmp, wr.line,
478                                                                      wrln_completion_callback_data);
479                                 list = g_completion_complete(gcmp, wr.line, &prefix);
480                                 if (prefix) {
481                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
482                                         cursor_move_to_eol(&wr);
483                                         g_free(prefix);
484                                 } else
485                                         screen_bell();
487                                 if (wrln_post_completion_callback)
488                                         wrln_post_completion_callback(gcmp, wr.line, list,
489                                                                       wrln_completion_callback_data);
490                         }
491 #endif
492                         break;
494                 case KEY_CTRL_G:
495                         screen_bell();
496                         if (history) {
497                                 g_free(hcurrent->data);
498                                 hcurrent->data = NULL;
499                                 *history = g_list_delete_link(*history, hcurrent);
500                         }
501                         return NULL;
503                 case KEY_LEFT:
504                 case KEY_CTRL_B:
505                         cursor_move_left(&wr);
506                         break;
507                 case KEY_RIGHT:
508                 case KEY_CTRL_F:
509                         cursor_move_right(&wr);
510                         break;
511                 case KEY_HOME:
512                 case KEY_CTRL_A:
513                         wr.cursor = 0;
514                         wr.start = 0;
515                         break;
516                 case KEY_END:
517                 case KEY_CTRL_E:
518                         cursor_move_to_eol(&wr);
519                         break;
520                 case KEY_CTRL_K:
521                         wr.line[wr.cursor] = 0;
522                         break;
523                 case KEY_CTRL_U:
524                         wr.cursor = utf8_width(wr.line);
525                         for (i = 0; i < wr.cursor; i++)
526                                 wr.line[i] = '\0';
527                         wr.cursor = 0;
528                         break;
529                 case KEY_CTRL_W:
530                         /* Firstly remove trailing spaces. */
531                         for (i = wr.cursor; i > 0 && wr.line[i-1] == ' '; i--)
532                         {
533                                 cursor_move_left(&wr);
534                                 wreadln_delete_char(&wr, wr.cursor);
535                         }
536                         /* Then remove word until next space. */
537                         for (; i > 0 && wr.line[i-1] != ' '; i--)
538                         {
539                                 cursor_move_left(&wr);
540                                 wreadln_delete_char(&wr, wr.cursor);
541                         }
542                         break;
543                 case 127:
544                 case KEY_BCKSPC:        /* handle backspace: copy all */
545                 case KEY_BACKSPACE:     /* chars starting from curpos */
546                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
547                                 cursor_move_left(&wr);
548                                 wreadln_delete_char(&wr, wr.cursor);
549                         }
550                         break;
551                 case KEY_DC:            /* handle delete key. As above */
552                 case KEY_CTRL_D:
553                         if (wr.line[wr.cursor] != 0)
554                                 wreadln_delete_char(&wr, wr.cursor);
555                         break;
556                 case KEY_UP:
557                 case KEY_CTRL_P:
558                         /* get previous history entry */
559                         if (history && hlist->prev) {
560                                 if (hlist == hcurrent)
561                                         /* save the current line */
562                                         g_strlcpy(hlist->data, wr.line,
563                                                   sizeof(wr.line));
565                                 /* get previous line */
566                                 hlist = hlist->prev;
567                                 g_strlcpy(wr.line, hlist->data,
568                                           sizeof(wr.line));
569                         }
570                         cursor_move_to_eol(&wr);
571                         break;
572                 case KEY_DOWN:
573                 case KEY_CTRL_N:
574                         /* get next history entry */
575                         if (history && hlist->next) {
576                                 /* get next line */
577                                 hlist = hlist->next;
578                                 g_strlcpy(wr.line, hlist->data,
579                                           sizeof(wr.line));
580                         }
581                         cursor_move_to_eol(&wr);
582                         break;
584                 case '\n':
585                 case 13:
586                 case KEY_IC:
587                 case KEY_PPAGE:
588                 case KEY_NPAGE:
589                 case KEY_F(1):
590                         /* ignore char */
591                         break;
592                 default:
593                         if (key >= 32)
594                                 wreadln_insert_byte(&wr, key);
595                 }
597                 drawline(&wr);
598         }
600         /* update history */
601         if (history) {
602                 if (strlen(wr.line)) {
603                         /* update the current history entry */
604                         size_t size = strlen(wr.line) + 1;
605                         hcurrent->data = g_realloc(hcurrent->data, size);
606                         g_strlcpy(hcurrent->data, wr.line, size);
607                 } else {
608                         /* the line was empty - remove the current history entry */
609                         g_free(hcurrent->data);
610                         hcurrent->data = NULL;
611                         *history = g_list_delete_link(*history, hcurrent);
612                 }
614                 while (g_list_length(*history) > wrln_max_history_length) {
615                         GList *first = g_list_first(*history);
617                         /* remove the oldest history entry  */
618                         g_free(first->data);
619                         first->data = NULL;
620                         *history = g_list_delete_link(*history, first);
621                 }
622         }
624         if (wr.line[0] == 0)
625                 return NULL;
627         return g_strdup(wr.line);
630 gchar *
631 wreadln(WINDOW *w,
632         const gchar *prompt,
633         const gchar *initial_value,
634         unsigned x1,
635         GList **history,
636         GCompletion *gcmp)
638         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
641 gchar *
642 wreadln_masked(WINDOW *w,
643                const gchar *prompt,
644                const gchar *initial_value,
645                unsigned x1,
646                GList **history,
647                GCompletion *gcmp)
649         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);