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.
22 #include "screen_utils.h"
30 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
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(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
92 assert(x
<= strlen(data
));
96 p
= locale_to_utf8(dup
);
99 width
= utf8_width(p
);
110 /** finds the first character which doesn't fit on the screen */
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
);
122 p
= locale_to_utf8(dup
);
123 p_width
= utf8_width(p
);
125 if (p_width
<= width
)
137 return (size_t)width
;
141 /** returns the screen column where the cursor is located */
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
152 right_align_bytes(const gchar
*data
, size_t right
, unsigned width
)
154 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
158 assert(right
<= strlen(data
));
160 dup
= g_strdup(data
);
163 while (dup
[start
] != 0) {
164 char *p
= locale_to_utf8(dup
+ start
), *q
;
165 unsigned p_width
= utf8_width(p
);
168 if (p_width
< width
) {
173 c
= g_utf8_get_char(p
);
174 p
[g_unichar_to_utf8(c
, NULL
)] = 0;
175 q
= utf8_to_locale(p
);
188 return right
>= width
? right
+ 1 - width
: 0;
192 /** returns the size (in bytes) of the next character */
194 next_char_size(const gchar
*data
)
196 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
197 char *p
= locale_to_utf8(data
), *q
;
201 c
= g_utf8_get_char(p
);
202 p
[g_unichar_to_utf8(c
, NULL
)] = 0;
203 q
= utf8_to_locale(p
);
217 /** returns the size (in bytes) of the previous character */
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
;
230 c
= g_utf8_get_char(q
);
231 size
= g_unichar_to_utf8(c
, NULL
);
250 /* move the cursor one step to the right */
251 static inline void cursor_move_right(struct wreadln
*wr
)
255 if (wr
->line
[wr
->cursor
] == 0)
258 size
= next_char_size(wr
->line
+ wr
->cursor
);
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
)
272 size
= prev_char_size(wr
->line
, wr
->cursor
);
273 assert(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 */
295 whline(wr
->w
, '*', utf8_width(wr
->line
+ wr
->start
));
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 */
305 #if defined(ENABLE_WIDE) || defined(ENABLE_MULTIBYTE)
307 multibyte_is_complete(const char *p
, size_t length
)
309 GError
*error
= NULL
;
310 gchar
*q
= g_locale_to_utf8(p
, length
,
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
};
329 struct pollfd pfd
= {
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 */
342 /* poll for more bytes on stdin, without timeout */
344 ret
= poll(&pfd
, 1, 0);
346 /* no more input from keyboard */
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
);
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
;
365 wr
->cursor
+= length
;
366 if (cursor_column(wr
) >= wr
->width
)
367 wr
->start
= right_align_bytes(wr
->line
, wr
->cursor
, wr
->width
);
371 wreadln_delete_char(struct wreadln
*wr
, size_t x
)
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 */
387 const gchar
*initial_value
,
393 struct wreadln wr
= {
399 GList
*hlist
= NULL
, *hcurrent
= NULL
;
409 /* make sure the cursor is visible */
411 /* print prompt string */
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
)
421 wr
.width
= x1
- wr
.x
;
422 /* clear input area */
423 mvwhline(w
, wr
.y
, wr
.x
, ' ', wr
.width
);
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
);
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 */
442 g_strlcpy(wr
.line
, hlist
->data
, sizeof(wr
.line
));
444 cursor_move_to_eol(&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
);
453 while (key
!= 13 && key
!= '\n') {
456 /* check if key is a function key */
457 for (i
= 0; i
< 63; i
++)
458 if (key
== (int)KEY_F(i
)) {
465 case KEY_MOUSE
: /* ignore mouse events */
467 case ERR
: /* ignore errors */
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
);
481 g_strlcpy(wr
.line
, prefix
, sizeof(wr
.line
));
482 cursor_move_to_eol(&wr
);
487 if (wrln_post_completion_callback
)
488 wrln_post_completion_callback(gcmp
, wr
.line
, list
,
489 wrln_completion_callback_data
);
497 g_free(hcurrent
->data
);
498 hcurrent
->data
= NULL
;
499 *history
= g_list_delete_link(*history
, hcurrent
);
505 cursor_move_left(&wr
);
509 cursor_move_right(&wr
);
518 cursor_move_to_eol(&wr
);
521 wr
.line
[wr
.cursor
] = 0;
524 wr
.cursor
= utf8_width(wr
.line
);
525 for (i
= 0; i
< wr
.cursor
; i
++)
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
);
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
);
551 case KEY_DC
: /* handle delete key. As above */
553 if (wr
.line
[wr
.cursor
] != 0)
554 wreadln_delete_char(&wr
, wr
.cursor
);
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
,
565 /* get previous line */
567 g_strlcpy(wr
.line
, hlist
->data
,
570 cursor_move_to_eol(&wr
);
574 /* get next history entry */
575 if (history
&& hlist
->next
) {
578 g_strlcpy(wr
.line
, hlist
->data
,
581 cursor_move_to_eol(&wr
);
594 wreadln_insert_byte(&wr
, key
);
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
);
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 */
620 *history
= g_list_delete_link(*history
, first
);
627 return g_strdup(wr
.line
);
633 const gchar
*initial_value
,
638 return _wreadln(w
, prompt
, initial_value
, x1
, history
, gcmp
, FALSE
);
642 wreadln_masked(WINDOW
*w
,
644 const gchar
*initial_value
,
649 return _wreadln(w
, prompt
, initial_value
, x1
, history
, gcmp
, TRUE
);