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.
22 #include "screen_utils.h"
30 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(WIN32)
51 /** the ncurses window where this field is displayed */
54 /** the origin coordinates in the window */
57 /** the screen width of the input field */
60 /** is the input masked, i.e. characters displayed as '*'? */
61 const gboolean masked
;
63 /** the byte position of the cursor */
66 /** the byte position displayed at the origin (for horizontal
70 /** the current value */
74 /** max items stored in the history list */
75 static const guint wrln_max_history_length
= 32;
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
;
83 /** converts a byte position to a screen column */
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
);
92 char *p
= replace_locale_to_utf8(dup
);
94 unsigned width
= utf8_width(p
);
105 /** finds the first character which doesn't fit on the screen */
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
);
115 char *p
= locale_to_utf8(dup
);
116 unsigned p_width
= utf8_width(p
);
118 if (p_width
<= width
)
130 return (size_t)width
;
134 /** returns the screen column where the cursor is located */
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
145 right_align_bytes(const gchar
*data
, size_t right
, unsigned width
)
147 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
151 assert(right
<= strlen(data
));
153 dup
= g_strdup(data
);
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
) {
165 gunichar c
= g_utf8_get_char(p
);
166 p
[g_unichar_to_utf8(c
, NULL
)] = 0;
167 q
= utf8_to_locale(p
);
180 return right
>= width
? right
+ 1 - width
: 0;
184 /** returns the size (in bytes) of the next character */
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
);
196 size_t size
= strlen(q
);
207 /** returns the size (in bytes) of the previous character */
209 prev_char_size(const gchar
*data
, size_t x
)
211 #if defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)
214 char *p
= locale_to_utf8(data
);
218 gunichar c
= g_utf8_get_char(q
);
219 size_t size
= g_unichar_to_utf8(c
, NULL
);
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)
244 size_t size
= next_char_size(wr
->line
+ wr
->cursor
);
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
)
256 size_t size
= prev_char_size(wr
->line
, wr
->cursor
);
257 assert(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 */
279 whline(wr
->w
, '*', utf8_width(wr
->line
+ wr
->start
));
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 */
289 #if (defined(HAVE_CURSES_ENHANCED) || defined(ENABLE_MULTIBYTE)) && !defined(WIN32)
291 multibyte_is_complete(const char *p
, size_t length
)
293 GError
*error
= NULL
;
294 gchar
*q
= g_locale_to_utf8(p
, length
,
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
};
313 struct pollfd pfd
= {
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 */
325 /* poll for more bytes on stdin, without timeout */
327 if (poll(&pfd
, 1, 0) <= 0)
328 /* no more input from keyboard */
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
);
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
;
347 wr
->cursor
+= length
;
348 if (cursor_column(wr
) >= wr
->width
)
349 wr
->start
= right_align_bytes(wr
->line
, wr
->cursor
, wr
->width
);
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 */
367 const gchar
*initial_value
,
373 struct wreadln wr
= {
379 GList
*hlist
= NULL
, *hcurrent
= NULL
;
387 /* make sure the cursor is visible */
389 /* print prompt string */
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
)
399 wr
.width
= x1
- wr
.x
;
400 /* clear input area */
401 mvwhline(w
, wr
.y
, wr
.x
, ' ', wr
.width
);
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
);
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 */
420 g_strlcpy(wr
.line
, hlist
->data
, sizeof(wr
.line
));
422 cursor_move_to_eol(&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
);
432 while (key
!= 13 && key
!= '\n') {
435 /* check if key is a function key */
436 for (size_t i
= 0; i
< 63; i
++)
437 if (key
== (int)KEY_F(i
)) {
446 case KEY_MOUSE
: /* ignore mouse events */
448 case ERR
: /* ignore errors */
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
);
462 g_strlcpy(wr
.line
, prefix
, sizeof(wr
.line
));
463 cursor_move_to_eol(&wr
);
468 if (wrln_post_completion_callback
)
469 wrln_post_completion_callback(gcmp
, wr
.line
, list
,
470 wrln_completion_callback_data
);
478 g_free(hcurrent
->data
);
479 hcurrent
->data
= NULL
;
480 *history
= g_list_delete_link(*history
, hcurrent
);
486 cursor_move_left(&wr
);
490 cursor_move_right(&wr
);
499 cursor_move_to_eol(&wr
);
502 wr
.line
[wr
.cursor
] = 0;
505 wr
.cursor
= utf8_width(wr
.line
);
506 for (i
= 0; i
< wr
.cursor
; i
++)
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
);
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
);
532 case KEY_DC
: /* handle delete key. As above */
534 if (wr
.line
[wr
.cursor
] != 0)
535 wreadln_delete_char(&wr
, wr
.cursor
);
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
,
546 /* get previous line */
548 g_strlcpy(wr
.line
, hlist
->data
,
551 cursor_move_to_eol(&wr
);
555 /* get next history entry */
556 if (history
&& hlist
->next
) {
559 g_strlcpy(wr
.line
, hlist
->data
,
562 cursor_move_to_eol(&wr
);
575 wreadln_insert_byte(&wr
, key
);
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
);
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 unsigned history_length
= g_list_length(*history
);
596 while (history_length
> wrln_max_history_length
) {
597 GList
*first
= g_list_first(*history
);
599 /* remove the oldest history entry */
602 *history
= g_list_delete_link(*history
, first
);
610 return g_strdup(wr
.line
);
616 const gchar
*initial_value
,
621 return _wreadln(w
, prompt
, initial_value
, x1
, history
, gcmp
, FALSE
);
625 wreadln_masked(WINDOW
*w
,
627 const gchar
*initial_value
,
632 return _wreadln(w
, prompt
, initial_value
, x1
, history
, gcmp
, TRUE
);