ncmpc version 0.15
[ncmpc.git] / src / wreadln.c
blobad1047fd5cc001365a886264958710190b390379
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.
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];
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)
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;
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;
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);
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;
240 q += size;
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;
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,
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);
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,
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, ": ");
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;
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));
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);
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;
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);
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);
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--)
533 cursor_move_left(&wr);
534 wreadln_delete_char(&wr, wr.cursor);
536 /* Then remove word until next space. */
537 for (; i > 0 && wr.line[i-1] != ' '; i--)
539 cursor_move_left(&wr);
540 wreadln_delete_char(&wr, wr.cursor);
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);
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));
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));
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);
597 drawline(&wr);
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);
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);
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);