Code

wreadln: wait for complete multibyte sequence in wreadln_insert_byte()
[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 void *wrln_completion_callback_data = NULL;
76 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
77 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
79 /* move the cursor one step to the right */
80 static inline void cursor_move_right(struct wreadln *wr)
81 {
82         if (wr->line[wr->cursor] == 0)
83                 return;
85         ++wr->cursor;
86         if (wr->cursor >= (size_t)wr->width &&
87             wr->start < wr->cursor - wr->width + 1)
88                 ++wr->start;
89 }
91 /* move the cursor one step to the left */
92 static inline void cursor_move_left(struct wreadln *wr)
93 {
94         if (wr->cursor == 0)
95                 return;
97         if (wr->cursor == wr->start && wr->start > 0)
98                 --wr->start;
99         --wr->cursor;
102 /* move the cursor to the end of the line */
103 static inline void cursor_move_to_eol(struct wreadln *wr)
105         wr->cursor = strlen(wr->line);
106         if (wr->cursor >= wr->width)
107                 wr->start = wr->cursor - wr->width + 1;
110 /* draw line buffer and update cursor position */
111 static inline void drawline(const struct wreadln *wr)
113         wmove(wr->w, wr->y, wr->x);
114         /* clear input area */
115         whline(wr->w, ' ', wr->width);
116         /* print visible part of the line buffer */
117         if (wr->masked)
118                 whline(wr->w, '*', utf8_width(wr->line) - wr->start);
119         else
120                 waddnstr(wr->w, wr->line + wr->start, wr->width);
121         /* move the cursor to the correct position */
122         wmove(wr->w, wr->y, wr->x + wr->cursor - wr->start);
123         /* tell ncurses to redraw the screen */
124         doupdate();
127 #ifdef ENABLE_WIDE
128 static bool
129 multibyte_is_complete(const char *p, size_t length)
131         GError *error = NULL;
132         gchar *q = g_locale_to_utf8(p, length,
133                                     NULL, NULL, &error);
134         if (q != NULL) {
135                 g_free(q);
136                 return true;
137         } else {
138                 g_error_free(error);
139                 return false;
140         }
142 #endif
144 static void
145 wreadln_insert_byte(struct wreadln *wr, gint key)
147         size_t rest = strlen(wr->line + wr->cursor) + 1;
148 #ifdef ENABLE_WIDE
149         char buffer[32] = { key };
150         size_t length = 1;
151         struct pollfd pfd = {
152                 .fd = 0,
153                 .events = POLLIN,
154         };
155         int ret;
157         /* wide version: try to complete the multibyte sequence */
159         while (length < sizeof(buffer)) {
160                 if (multibyte_is_complete(buffer, length))
161                         /* sequence is complete */
162                         break;
164                 /* poll for more bytes on stdin, without timeout */
166                 ret = poll(&pfd, 1, 0);
167                 if (ret <= 0)
168                         /* no more input from keyboard */
169                         break;
171                 buffer[length++] = wgetch(wr->w);
172         }
174         memmove(wr->line + wr->cursor + length,
175                 wr->line + wr->cursor, rest);
176         memcpy(wr->line + wr->cursor, buffer, length);
178 #else
179         const size_t length = 1;
181         memmove(wr->line + wr->cursor + length,
182                 wr->line + wr->cursor, rest);
183         wr->line[wr->cursor] = key;
185 #endif
187         wr->cursor += length;
188         if (wr->cursor >= (size_t)wr->width &&
189             wr->start < wr->cursor - wr->width + 1)
190                 wr->start += length;
193 static void
194 wreadln_delete_char(struct wreadln *wr, size_t x)
196         size_t rest;
197         const size_t length = 1;
199         assert(x < strlen(wr->line));
201         rest = strlen(&wr->line[x + length]) + 1;
202         memmove(&wr->line[x], &wr->line[x + length], rest);
205 /* libcurses version */
207 static gchar *
208 _wreadln(WINDOW *w,
209          const gchar *prompt,
210          const gchar *initial_value,
211          unsigned x1,
212          GList **history,
213          GCompletion *gcmp,
214          gboolean masked)
216         struct wreadln wr = {
217                 .w = w,
218                 .masked = masked,
219                 .cursor = 0,
220                 .start = 0,
221         };
222         GList *hlist = NULL, *hcurrent = NULL;
223         gint key = 0;
224         size_t i;
226         /* turn off echo */
227         noecho();
228         /* make shure the cursor is visible */
229         curs_set(1);
230         /* print prompt string */
231         if (prompt)
232                 waddstr(w, prompt);
233         /* retrive y and x0 position */
234         getyx(w, wr.y, wr.x);
235         /* check the x1 value */
236         if (x1 <= wr.x || x1 > (unsigned)COLS)
237                 x1 = COLS;
238         wr.width = x1 - wr.x;
239         /* clear input area */
240         mvwhline(w, wr.y, wr.x, ' ', wr.width);
242         if (history) {
243                 /* append the a new line to our history list */
244                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
245                 /* hlist points to the current item in the history list */
246                 hlist = g_list_last(*history);
247                 hcurrent = hlist;
248         }
250         if (initial_value == (char *)-1) {
251                 /* get previous history entry */
252                 if (history && hlist->prev) {
253                         if (hlist == hcurrent)
254                                 /* save the current line */
255                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
257                         /* get previous line */
258                         hlist = hlist->prev;
259                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
260                 }
261                 cursor_move_to_eol(&wr);
262                 drawline(&wr);
263         } else if (initial_value) {
264                 /* copy the initial value to the line buffer */
265                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
266                 cursor_move_to_eol(&wr);
267                 drawline(&wr);
268         }
270         while (key != 13 && key != '\n') {
271                 key = wgetch(w);
273                 /* check if key is a function key */
274                 for (i = 0; i < 63; i++)
275                         if (key == (int)KEY_F(i)) {
276                                 key = KEY_F(1);
277                                 i = 64;
278                         }
280                 switch (key) {
281 #ifdef HAVE_GETMOUSE
282                 case KEY_MOUSE: /* ignore mouse events */
283 #endif
284                 case ERR: /* ingnore errors */
285                         break;
287                 case TAB:
288                         if (gcmp) {
289                                 char *prefix = NULL;
290                                 GList *list;
292                                 if (wrln_pre_completion_callback)
293                                         wrln_pre_completion_callback(gcmp, wr.line,
294                                                                      wrln_completion_callback_data);
295                                 list = g_completion_complete(gcmp, wr.line, &prefix);
296                                 if (prefix) {
297                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
298                                         cursor_move_to_eol(&wr);
299                                         g_free(prefix);
300                                 } else
301                                         screen_bell();
303                                 if (wrln_post_completion_callback)
304                                         wrln_post_completion_callback(gcmp, wr.line, list,
305                                                                       wrln_completion_callback_data);
306                         }
307                         break;
309                 case KEY_CTRL_G:
310                         screen_bell();
311                         if (history) {
312                                 g_free(hcurrent->data);
313                                 hcurrent->data = NULL;
314                                 *history = g_list_delete_link(*history, hcurrent);
315                         }
316                         return NULL;
318                 case KEY_LEFT:
319                 case KEY_CTRL_B:
320                         cursor_move_left(&wr);
321                         break;
322                 case KEY_RIGHT:
323                 case KEY_CTRL_F:
324                         cursor_move_right(&wr);
325                         break;
326                 case KEY_HOME:
327                 case KEY_CTRL_A:
328                         wr.cursor = 0;
329                         wr.start = 0;
330                         break;
331                 case KEY_END:
332                 case KEY_CTRL_E:
333                         cursor_move_to_eol(&wr);
334                         break;
335                 case KEY_CTRL_K:
336                         wr.line[wr.cursor] = 0;
337                         break;
338                 case KEY_CTRL_U:
339                         wr.cursor = utf8_width(wr.line);
340                         for (i = 0; i < wr.cursor; i++)
341                                 wr.line[i] = '\0';
342                         wr.cursor = 0;
343                         break;
344                 case 127:
345                 case KEY_BCKSPC:        /* handle backspace: copy all */
346                 case KEY_BACKSPACE:     /* chars starting from curpos */
347                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
348                                 cursor_move_left(&wr);
349                                 wreadln_delete_char(&wr, wr.cursor);
350                         }
351                         break;
352                 case KEY_DC:            /* handle delete key. As above */
353                 case KEY_CTRL_D:
354                         if (wr.line[wr.cursor] != 0)
355                                 wreadln_delete_char(&wr, wr.cursor);
356                         break;
357                 case KEY_UP:
358                 case KEY_CTRL_P:
359                         /* get previous history entry */
360                         if (history && hlist->prev) {
361                                 if (hlist == hcurrent)
362                                         /* save the current line */
363                                         g_strlcpy(hlist->data, wr.line,
364                                                   sizeof(wr.line));
366                                 /* get previous line */
367                                 hlist = hlist->prev;
368                                 g_strlcpy(wr.line, hlist->data,
369                                           sizeof(wr.line));
370                         }
371                         cursor_move_to_eol(&wr);
372                         break;
373                 case KEY_DOWN:
374                 case KEY_CTRL_N:
375                         /* get next history entry */
376                         if (history && hlist->next) {
377                                 /* get next line */
378                                 hlist = hlist->next;
379                                 g_strlcpy(wr.line, hlist->data,
380                                           sizeof(wr.line));
381                         }
382                         cursor_move_to_eol(&wr);
383                         break;
385                 case '\n':
386                 case 13:
387                 case KEY_IC:
388                 case KEY_PPAGE:
389                 case KEY_NPAGE:
390                 case KEY_F(1):
391                         /* ignore char */
392                         break;
393                 default:
394                         if (key >= 32)
395                                 wreadln_insert_byte(&wr, key);
396                 }
398                 drawline(&wr);
399         }
401         /* update history */
402         if (history) {
403                 if (strlen(wr.line)) {
404                         /* update the current history entry */
405                         size_t size = strlen(wr.line) + 1;
406                         hcurrent->data = g_realloc(hcurrent->data, size);
407                         g_strlcpy(hcurrent->data, wr.line, size);
408                 } else {
409                         /* the line was empty - remove the current history entry */
410                         g_free(hcurrent->data);
411                         hcurrent->data = NULL;
412                         *history = g_list_delete_link(*history, hcurrent);
413                 }
415                 while (g_list_length(*history) > wrln_max_history_length) {
416                         GList *first = g_list_first(*history);
418                         /* remove the oldest history entry  */
419                         g_free(first->data);
420                         first->data = NULL;
421                         *history = g_list_delete_link(*history, first);
422                 }
423         }
425         return g_strdup(wr.line);
428 gchar *
429 wreadln(WINDOW *w,
430         const gchar *prompt,
431         const gchar *initial_value,
432         unsigned x1,
433         GList **history,
434         GCompletion *gcmp)
436         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
439 gchar *
440 wreadln_masked(WINDOW *w,
441                const gchar *prompt,
442                const gchar *initial_value,
443                unsigned x1,
444                GList **history,
445                GCompletion *gcmp)
447         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);