screen: remove redundant #ifndef
[ncmpc.git] / src / wreadln.c
blobf45e474e1e2107f7986f8c66cb6819ab32578ab7
1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 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(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];
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(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;
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;
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);
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;
228 q += size;
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;
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,
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);
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,
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, ": ");
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;
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));
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);
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;
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);
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);
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--)
514 cursor_move_left(&wr);
515 wreadln_delete_char(&wr, wr.cursor);
517 /* Then remove word until next space. */
518 for (; i > 0 && wr.line[i-1] != ' '; i--)
520 cursor_move_left(&wr);
521 wreadln_delete_char(&wr, wr.cursor);
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);
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));
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));
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);
578 drawline(&wr);
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);
595 while (g_list_length(*history) > wrln_max_history_length) {
596 GList *first = g_list_first(*history);
598 /* remove the oldest history entry */
599 g_free(first->data);
600 first->data = NULL;
601 *history = g_list_delete_link(*history, first);
605 if (wr.line[0] == 0)
606 return NULL;
608 return g_strdup(wr.line);
611 gchar *
612 wreadln(WINDOW *w,
613 const gchar *prompt,
614 const gchar *initial_value,
615 unsigned x1,
616 GList **history,
617 GCompletion *gcmp)
619 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
622 gchar *
623 wreadln_masked(WINDOW *w,
624 const gchar *prompt,
625 const gchar *initial_value,
626 unsigned x1,
627 GList **history,
628 GCompletion *gcmp)
630 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);