Code

wreadln: return NULL instead of empty string
[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 "screen_utils.h"
22 #include "config.h"
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
29 #ifdef ENABLE_WIDE
30 #include <sys/poll.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 struct wreadln {
49         /** the ncurses window where this field is displayed */
50         WINDOW *const w;
52         /** the origin coordinates in the window */
53         unsigned x, y;
55         /** the screen width of the input field */
56         unsigned width;
58         /** is the input masked, i.e. characters displayed as '*'? */
59         const gboolean masked;
61         /** the byte position of the cursor */
62         size_t cursor;
64         /** the byte position displayed at the origin (for horizontal
65             scrolling) */
66         size_t start;
68         /** the current value */
69         gchar line[1024];
70 };
72 /** max items stored in the history list */
73 static const guint wrln_max_history_length = 32;
75 #ifndef NCMPC_MINI
76 void *wrln_completion_callback_data = NULL;
77 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
78 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
79 #endif
81 /** converts a byte position to a screen column */
82 static unsigned
83 byte_to_screen(const gchar *data, size_t x)
84 {
85 #ifdef ENABLE_WIDE
86         gchar *dup;
87         char *p;
88         unsigned width;
90         assert(x <= strlen(data));
92         dup = g_strdup(data);
93         dup[x] = 0;
94         p = locale_to_utf8(dup);
95         g_free(dup);
97         width = utf8_width(p);
98         g_free(p);
100         return width;
101 #else
102         (void)data;
104         return (unsigned)x;
105 #endif
108 /** finds the first character which doesn't fit on the screen */
109 static size_t
110 screen_to_bytes(const gchar *data, unsigned width)
112 #ifdef ENABLE_WIDE
113         size_t length = strlen(data);
114         gchar *dup = g_strdup(data);
115         char *p;
116         unsigned p_width;
118         while (true) {
119                 dup[length] = 0;
120                 p = locale_to_utf8(dup);
121                 p_width = utf8_width(p);
122                 g_free(p);
123                 if (p_width <= width)
124                         break;
126                 --length;
127         }
129         g_free(dup);
131         return length;
132 #else
133         (void)data;
135         return (size_t)width;
136 #endif
139 /** returns the screen colum where the cursor is located */
140 static unsigned
141 cursor_column(const struct wreadln *wr)
143         return byte_to_screen(wr->line + wr->start,
144                               wr->cursor - wr->start);
147 /** returns the offset in the string to align it at the right border
148     of the screen */
149 static inline size_t
150 right_align_bytes(const gchar *data, size_t right, unsigned width)
152 #ifdef ENABLE_WIDE
153         gchar *dup;
154         size_t start = 0;
156         assert(right <= strlen(data));
158         dup = g_strdup(data);
159         dup[right] = 0;
161         while (dup[start] != 0) {
162                 char *p = locale_to_utf8(dup + start), *q;
163                 unsigned p_width = utf8_width(p);
164                 gunichar c;
166                 if (p_width < width) {
167                         g_free(p);
168                         break;
169                 }
171                 c = g_utf8_get_char(p);
172                 p[g_unichar_to_utf8(c, NULL)] = 0;
173                 q = utf8_to_locale(p);
174                 g_free(p);
176                 start += strlen(q);
177                 g_free(q);
178         }
180         g_free(dup);
182         return start;
183 #else
184         (void)data;
186         return right >= width ? right + 1 - width : 0;
187 #endif
190 /** returns the size (in bytes) of the next character */
191 static inline size_t
192 next_char_size(const gchar *data)
194 #ifdef ENABLE_WIDE
195         char *p = locale_to_utf8(data), *q;
196         gunichar c;
197         size_t size;
199         c = g_utf8_get_char(p);
200         p[g_unichar_to_utf8(c, NULL)] = 0;
201         q = utf8_to_locale(p);
202         g_free(p);
204         size = strlen(q);
205         g_free(q);
207         return size;
208 #else
209         (void)data;
211         return 1;
212 #endif
215 /** returns the size (in bytes) of the previous character */
216 static inline size_t
217 prev_char_size(const gchar *data, size_t x)
219 #ifdef ENABLE_WIDE
220         char *p = locale_to_utf8(data), *q;
221         gunichar c;
222         size_t size;
224         assert(x > 0);
226         q = p;
227         while (true) {
228                 c = g_utf8_get_char(q);
229                 size = g_unichar_to_utf8(c, NULL);
230                 if (size > x)
231                         size = x;
232                 x -= size;
233                 if (x == 0) {
234                         g_free(p);
235                         return size;
236                 }
238                 q += size;
239         }
240 #else
241         (void)data;
242         (void)x;
244         return 1;
245 #endif
248 /* move the cursor one step to the right */
249 static inline void cursor_move_right(struct wreadln *wr)
251         size_t size;
253         if (wr->line[wr->cursor] == 0)
254                 return;
256         size = next_char_size(wr->line + wr->cursor);
257         wr->cursor += size;
258         if (cursor_column(wr) >= wr->width)
259                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
262 /* move the cursor one step to the left */
263 static inline void cursor_move_left(struct wreadln *wr)
265         size_t size;
267         if (wr->cursor == 0)
268                 return;
270         size = prev_char_size(wr->line, wr->cursor);
271         assert(wr->cursor >= size);
272         wr->cursor -= size;
273         if (wr->cursor < wr->start)
274                 wr->start = wr->cursor;
277 /* move the cursor to the end of the line */
278 static inline void cursor_move_to_eol(struct wreadln *wr)
280         wr->cursor = strlen(wr->line);
281         if (cursor_column(wr) >= wr->width)
282                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
285 /* draw line buffer and update cursor position */
286 static inline void drawline(const struct wreadln *wr)
288         wmove(wr->w, wr->y, wr->x);
289         /* clear input area */
290         whline(wr->w, ' ', wr->width);
291         /* print visible part of the line buffer */
292         if (wr->masked)
293                 whline(wr->w, '*', utf8_width(wr->line + wr->start));
294         else
295                 waddnstr(wr->w, wr->line + wr->start,
296                          screen_to_bytes(wr->line, wr->width));
297         /* move the cursor to the correct position */
298         wmove(wr->w, wr->y, wr->x + cursor_column(wr));
299         /* tell ncurses to redraw the screen */
300         doupdate();
303 #ifdef ENABLE_WIDE
304 static bool
305 multibyte_is_complete(const char *p, size_t length)
307         GError *error = NULL;
308         gchar *q = g_locale_to_utf8(p, length,
309                                     NULL, NULL, &error);
310         if (q != NULL) {
311                 g_free(q);
312                 return true;
313         } else {
314                 g_error_free(error);
315                 return false;
316         }
318 #endif
320 static void
321 wreadln_insert_byte(struct wreadln *wr, gint key)
323         size_t rest = strlen(wr->line + wr->cursor) + 1;
324 #ifdef ENABLE_WIDE
325         char buffer[32] = { key };
326         size_t length = 1;
327         struct pollfd pfd = {
328                 .fd = 0,
329                 .events = POLLIN,
330         };
331         int ret;
333         /* wide version: try to complete the multibyte sequence */
335         while (length < sizeof(buffer)) {
336                 if (multibyte_is_complete(buffer, length))
337                         /* sequence is complete */
338                         break;
340                 /* poll for more bytes on stdin, without timeout */
342                 ret = poll(&pfd, 1, 0);
343                 if (ret <= 0)
344                         /* no more input from keyboard */
345                         break;
347                 buffer[length++] = wgetch(wr->w);
348         }
350         memmove(wr->line + wr->cursor + length,
351                 wr->line + wr->cursor, rest);
352         memcpy(wr->line + wr->cursor, buffer, length);
354 #else
355         const size_t length = 1;
357         memmove(wr->line + wr->cursor + length,
358                 wr->line + wr->cursor, rest);
359         wr->line[wr->cursor] = key;
361 #endif
363         wr->cursor += length;
364         if (cursor_column(wr) >= wr->width)
365                 wr->start = right_align_bytes(wr->line, wr->cursor, wr->width);
368 static void
369 wreadln_delete_char(struct wreadln *wr, size_t x)
371         size_t rest, length;
373         assert(x < strlen(wr->line));
375         length = next_char_size(&wr->line[x]);
376         rest = strlen(&wr->line[x + length]) + 1;
377         memmove(&wr->line[x], &wr->line[x + length], rest);
380 /* libcurses version */
382 static gchar *
383 _wreadln(WINDOW *w,
384          const gchar *prompt,
385          const gchar *initial_value,
386          unsigned x1,
387          GList **history,
388          GCompletion *gcmp,
389          gboolean masked)
391         struct wreadln wr = {
392                 .w = w,
393                 .masked = masked,
394                 .cursor = 0,
395                 .start = 0,
396         };
397         GList *hlist = NULL, *hcurrent = NULL;
398         gint key = 0;
399         size_t i;
401 #ifdef NCMPC_MINI
402         (void)gcmp;
403 #endif
405         /* turn off echo */
406         noecho();
407         /* make shure the cursor is visible */
408         curs_set(1);
409         /* print prompt string */
410         if (prompt)
411                 waddstr(w, prompt);
412         /* retrive y and x0 position */
413         getyx(w, wr.y, wr.x);
414         /* check the x1 value */
415         if (x1 <= wr.x || x1 > (unsigned)COLS)
416                 x1 = COLS;
417         wr.width = x1 - wr.x;
418         /* clear input area */
419         mvwhline(w, wr.y, wr.x, ' ', wr.width);
421         if (history) {
422                 /* append the a new line to our history list */
423                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
424                 /* hlist points to the current item in the history list */
425                 hlist = g_list_last(*history);
426                 hcurrent = hlist;
427         }
429         if (initial_value == (char *)-1) {
430                 /* get previous history entry */
431                 if (history && hlist->prev) {
432                         if (hlist == hcurrent)
433                                 /* save the current line */
434                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
436                         /* get previous line */
437                         hlist = hlist->prev;
438                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
439                 }
440                 cursor_move_to_eol(&wr);
441                 drawline(&wr);
442         } else if (initial_value) {
443                 /* copy the initial value to the line buffer */
444                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
445                 cursor_move_to_eol(&wr);
446                 drawline(&wr);
447         }
449         while (key != 13 && key != '\n') {
450                 key = wgetch(w);
452                 /* check if key is a function key */
453                 for (i = 0; i < 63; i++)
454                         if (key == (int)KEY_F(i)) {
455                                 key = KEY_F(1);
456                                 i = 64;
457                         }
459                 switch (key) {
460 #ifdef HAVE_GETMOUSE
461                 case KEY_MOUSE: /* ignore mouse events */
462 #endif
463                 case ERR: /* ingnore errors */
464                         break;
466                 case TAB:
467 #ifndef NCMPC_MINI
468                         if (gcmp) {
469                                 char *prefix = NULL;
470                                 GList *list;
472                                 if (wrln_pre_completion_callback)
473                                         wrln_pre_completion_callback(gcmp, wr.line,
474                                                                      wrln_completion_callback_data);
475                                 list = g_completion_complete(gcmp, wr.line, &prefix);
476                                 if (prefix) {
477                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
478                                         cursor_move_to_eol(&wr);
479                                         g_free(prefix);
480                                 } else
481                                         screen_bell();
483                                 if (wrln_post_completion_callback)
484                                         wrln_post_completion_callback(gcmp, wr.line, list,
485                                                                       wrln_completion_callback_data);
486                         }
487 #endif
488                         break;
490                 case KEY_CTRL_G:
491                         screen_bell();
492                         if (history) {
493                                 g_free(hcurrent->data);
494                                 hcurrent->data = NULL;
495                                 *history = g_list_delete_link(*history, hcurrent);
496                         }
497                         return NULL;
499                 case KEY_LEFT:
500                 case KEY_CTRL_B:
501                         cursor_move_left(&wr);
502                         break;
503                 case KEY_RIGHT:
504                 case KEY_CTRL_F:
505                         cursor_move_right(&wr);
506                         break;
507                 case KEY_HOME:
508                 case KEY_CTRL_A:
509                         wr.cursor = 0;
510                         wr.start = 0;
511                         break;
512                 case KEY_END:
513                 case KEY_CTRL_E:
514                         cursor_move_to_eol(&wr);
515                         break;
516                 case KEY_CTRL_K:
517                         wr.line[wr.cursor] = 0;
518                         break;
519                 case KEY_CTRL_U:
520                         wr.cursor = utf8_width(wr.line);
521                         for (i = 0; i < wr.cursor; i++)
522                                 wr.line[i] = '\0';
523                         wr.cursor = 0;
524                         break;
525                 case 127:
526                 case KEY_BCKSPC:        /* handle backspace: copy all */
527                 case KEY_BACKSPACE:     /* chars starting from curpos */
528                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
529                                 cursor_move_left(&wr);
530                                 wreadln_delete_char(&wr, wr.cursor);
531                         }
532                         break;
533                 case KEY_DC:            /* handle delete key. As above */
534                 case KEY_CTRL_D:
535                         if (wr.line[wr.cursor] != 0)
536                                 wreadln_delete_char(&wr, wr.cursor);
537                         break;
538                 case KEY_UP:
539                 case KEY_CTRL_P:
540                         /* get previous history entry */
541                         if (history && hlist->prev) {
542                                 if (hlist == hcurrent)
543                                         /* save the current line */
544                                         g_strlcpy(hlist->data, wr.line,
545                                                   sizeof(wr.line));
547                                 /* get previous line */
548                                 hlist = hlist->prev;
549                                 g_strlcpy(wr.line, hlist->data,
550                                           sizeof(wr.line));
551                         }
552                         cursor_move_to_eol(&wr);
553                         break;
554                 case KEY_DOWN:
555                 case KEY_CTRL_N:
556                         /* get next history entry */
557                         if (history && hlist->next) {
558                                 /* get next line */
559                                 hlist = hlist->next;
560                                 g_strlcpy(wr.line, hlist->data,
561                                           sizeof(wr.line));
562                         }
563                         cursor_move_to_eol(&wr);
564                         break;
566                 case '\n':
567                 case 13:
568                 case KEY_IC:
569                 case KEY_PPAGE:
570                 case KEY_NPAGE:
571                 case KEY_F(1):
572                         /* ignore char */
573                         break;
574                 default:
575                         if (key >= 32)
576                                 wreadln_insert_byte(&wr, key);
577                 }
579                 drawline(&wr);
580         }
582         /* update history */
583         if (history) {
584                 if (strlen(wr.line)) {
585                         /* update the current history entry */
586                         size_t size = strlen(wr.line) + 1;
587                         hcurrent->data = g_realloc(hcurrent->data, size);
588                         g_strlcpy(hcurrent->data, wr.line, size);
589                 } else {
590                         /* the line was empty - remove the current history entry */
591                         g_free(hcurrent->data);
592                         hcurrent->data = NULL;
593                         *history = g_list_delete_link(*history, hcurrent);
594                 }
596                 while (g_list_length(*history) > 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                 }
604         }
606         if (wr.line[0] == 0)
607                 return NULL;
609         return g_strdup(wr.line);
612 gchar *
613 wreadln(WINDOW *w,
614         const gchar *prompt,
615         const gchar *initial_value,
616         unsigned x1,
617         GList **history,
618         GCompletion *gcmp)
620         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
623 gchar *
624 wreadln_masked(WINDOW *w,
625                const gchar *prompt,
626                const gchar *initial_value,
627                unsigned x1,
628                GList **history,
629                GCompletion *gcmp)
631         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);