Code

926f223b7fcdc031347cb811370be1a7fdf920cb
[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 #define KEY_CTRL_A   1
30 #define KEY_CTRL_B   2
31 #define KEY_CTRL_C   3
32 #define KEY_CTRL_D   4
33 #define KEY_CTRL_E   5
34 #define KEY_CTRL_F   6
35 #define KEY_CTRL_G   7
36 #define KEY_CTRL_K   11
37 #define KEY_CTRL_N   14
38 #define KEY_CTRL_P   16
39 #define KEY_CTRL_U   21
40 #define KEY_CTRL_Z   26
41 #define KEY_BCKSPC   8
42 #define TAB          9
44 struct wreadln {
45         /** the ncurses window where this field is displayed */
46         WINDOW *const w;
48         /** the origin coordinates in the window */
49         unsigned x, y;
51         /** the screen width of the input field */
52         unsigned width;
54         /** is the input masked, i.e. characters displayed as '*'? */
55         const gboolean masked;
57         /** the byte position of the cursor */
58         size_t cursor;
60         /** the byte position displayed at the origin (for horizontal
61             scrolling) */
62         size_t start;
64         /** the current value */
65         gchar line[1024];
66 };
68 /** max items stored in the history list */
69 static const guint wrln_max_history_length = 32;
71 void *wrln_completion_callback_data = NULL;
72 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
73 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
75 /* move the cursor one step to the right */
76 static inline void cursor_move_right(struct wreadln *wr)
77 {
78         if (wr->line[wr->cursor] == 0)
79                 return;
81         ++wr->cursor;
82         if (wr->cursor >= (size_t)wr->width &&
83             wr->start < wr->cursor - wr->width + 1)
84                 ++wr->start;
85 }
87 /* move the cursor one step to the left */
88 static inline void cursor_move_left(struct wreadln *wr)
89 {
90         if (wr->cursor == 0)
91                 return;
93         if (wr->cursor == wr->start && wr->start > 0)
94                 --wr->start;
95         --wr->cursor;
96 }
98 /* move the cursor to the end of the line */
99 static inline void cursor_move_to_eol(struct wreadln *wr)
101         wr->cursor = strlen(wr->line);
102         if (wr->cursor >= wr->width)
103                 wr->start = wr->cursor - wr->width + 1;
106 /* draw line buffer and update cursor position */
107 static inline void drawline(const struct wreadln *wr)
109         wmove(wr->w, wr->y, wr->x);
110         /* clear input area */
111         whline(wr->w, ' ', wr->width);
112         /* print visible part of the line buffer */
113         if (wr->masked)
114                 whline(wr->w, '*', utf8_width(wr->line) - wr->start);
115         else
116                 waddnstr(wr->w, wr->line + wr->start, wr->width);
117         /* move the cursor to the correct position */
118         wmove(wr->w, wr->y, wr->x + wr->cursor - wr->start);
119         /* tell ncurses to redraw the screen */
120         doupdate();
123 static void
124 wreadln_insert_byte(struct wreadln *wr, gint key)
126         size_t rest = strlen(wr->line + wr->cursor) + 1;
127         const size_t length = 1;
129         memmove(wr->line + wr->cursor + length,
130                 wr->line + wr->cursor, rest);
131         wr->line[wr->cursor] = key;
133         wr->cursor += length;
134         if (wr->cursor >= (size_t)wr->width &&
135             wr->start < wr->cursor - wr->width + 1)
136                 wr->start += length;
139 static void
140 wreadln_delete_char(struct wreadln *wr, size_t x)
142         size_t rest;
143         const size_t length = 1;
145         assert(x < strlen(wr->line));
147         rest = strlen(&wr->line[x + length]) + 1;
148         memmove(&wr->line[x], &wr->line[x + length], rest);
151 /* libcurses version */
153 static gchar *
154 _wreadln(WINDOW *w,
155          const gchar *prompt,
156          const gchar *initial_value,
157          unsigned x1,
158          GList **history,
159          GCompletion *gcmp,
160          gboolean masked)
162         struct wreadln wr = {
163                 .w = w,
164                 .masked = masked,
165                 .cursor = 0,
166                 .start = 0,
167         };
168         GList *hlist = NULL, *hcurrent = NULL;
169         gint key = 0;
170         size_t i;
172         /* turn off echo */
173         noecho();
174         /* make shure the cursor is visible */
175         curs_set(1);
176         /* print prompt string */
177         if (prompt)
178                 waddstr(w, prompt);
179         /* retrive y and x0 position */
180         getyx(w, wr.y, wr.x);
181         /* check the x1 value */
182         if (x1 <= wr.x || x1 > (unsigned)COLS)
183                 x1 = COLS;
184         wr.width = x1 - wr.x;
185         /* clear input area */
186         mvwhline(w, wr.y, wr.x, ' ', wr.width);
188         if (history) {
189                 /* append the a new line to our history list */
190                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
191                 /* hlist points to the current item in the history list */
192                 hlist = g_list_last(*history);
193                 hcurrent = hlist;
194         }
196         if (initial_value == (char *)-1) {
197                 /* get previous history entry */
198                 if (history && hlist->prev) {
199                         if (hlist == hcurrent)
200                                 /* save the current line */
201                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
203                         /* get previous line */
204                         hlist = hlist->prev;
205                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
206                 }
207                 cursor_move_to_eol(&wr);
208                 drawline(&wr);
209         } else if (initial_value) {
210                 /* copy the initial value to the line buffer */
211                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
212                 cursor_move_to_eol(&wr);
213                 drawline(&wr);
214         }
216         while (key != 13 && key != '\n') {
217                 key = wgetch(w);
219                 /* check if key is a function key */
220                 for (i = 0; i < 63; i++)
221                         if (key == (int)KEY_F(i)) {
222                                 key = KEY_F(1);
223                                 i = 64;
224                         }
226                 switch (key) {
227 #ifdef HAVE_GETMOUSE
228                 case KEY_MOUSE: /* ignore mouse events */
229 #endif
230                 case ERR: /* ingnore errors */
231                         break;
233                 case TAB:
234                         if (gcmp) {
235                                 char *prefix = NULL;
236                                 GList *list;
238                                 if (wrln_pre_completion_callback)
239                                         wrln_pre_completion_callback(gcmp, wr.line,
240                                                                      wrln_completion_callback_data);
241                                 list = g_completion_complete(gcmp, wr.line, &prefix);
242                                 if (prefix) {
243                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
244                                         cursor_move_to_eol(&wr);
245                                         g_free(prefix);
246                                 } else
247                                         screen_bell();
249                                 if (wrln_post_completion_callback)
250                                         wrln_post_completion_callback(gcmp, wr.line, list,
251                                                                       wrln_completion_callback_data);
252                         }
253                         break;
255                 case KEY_CTRL_G:
256                         screen_bell();
257                         if (history) {
258                                 g_free(hcurrent->data);
259                                 hcurrent->data = NULL;
260                                 *history = g_list_delete_link(*history, hcurrent);
261                         }
262                         return NULL;
264                 case KEY_LEFT:
265                 case KEY_CTRL_B:
266                         cursor_move_left(&wr);
267                         break;
268                 case KEY_RIGHT:
269                 case KEY_CTRL_F:
270                         cursor_move_right(&wr);
271                         break;
272                 case KEY_HOME:
273                 case KEY_CTRL_A:
274                         wr.cursor = 0;
275                         wr.start = 0;
276                         break;
277                 case KEY_END:
278                 case KEY_CTRL_E:
279                         cursor_move_to_eol(&wr);
280                         break;
281                 case KEY_CTRL_K:
282                         wr.line[wr.cursor] = 0;
283                         break;
284                 case KEY_CTRL_U:
285                         wr.cursor = utf8_width(wr.line);
286                         for (i = 0; i < wr.cursor; i++)
287                                 wr.line[i] = '\0';
288                         wr.cursor = 0;
289                         break;
290                 case 127:
291                 case KEY_BCKSPC:        /* handle backspace: copy all */
292                 case KEY_BACKSPACE:     /* chars starting from curpos */
293                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
294                                 cursor_move_left(&wr);
295                                 wreadln_delete_char(&wr, wr.cursor);
296                         }
297                         break;
298                 case KEY_DC:            /* handle delete key. As above */
299                 case KEY_CTRL_D:
300                         if (wr.line[wr.cursor] != 0)
301                                 wreadln_delete_char(&wr, wr.cursor);
302                         break;
303                 case KEY_UP:
304                 case KEY_CTRL_P:
305                         /* get previous history entry */
306                         if (history && hlist->prev) {
307                                 if (hlist == hcurrent)
308                                         /* save the current line */
309                                         g_strlcpy(hlist->data, wr.line,
310                                                   sizeof(wr.line));
312                                 /* get previous line */
313                                 hlist = hlist->prev;
314                                 g_strlcpy(wr.line, hlist->data,
315                                           sizeof(wr.line));
316                         }
317                         cursor_move_to_eol(&wr);
318                         break;
319                 case KEY_DOWN:
320                 case KEY_CTRL_N:
321                         /* get next history entry */
322                         if (history && hlist->next) {
323                                 /* get next line */
324                                 hlist = hlist->next;
325                                 g_strlcpy(wr.line, hlist->data,
326                                           sizeof(wr.line));
327                         }
328                         cursor_move_to_eol(&wr);
329                         break;
331                 case '\n':
332                 case 13:
333                 case KEY_IC:
334                 case KEY_PPAGE:
335                 case KEY_NPAGE:
336                 case KEY_F(1):
337                         /* ignore char */
338                         break;
339                 default:
340                         if (key >= 32)
341                                 wreadln_insert_byte(&wr, key);
342                 }
344                 drawline(&wr);
345         }
347         /* update history */
348         if (history) {
349                 if (strlen(wr.line)) {
350                         /* update the current history entry */
351                         size_t size = strlen(wr.line) + 1;
352                         hcurrent->data = g_realloc(hcurrent->data, size);
353                         g_strlcpy(hcurrent->data, wr.line, size);
354                 } else {
355                         /* the line was empty - remove the current history entry */
356                         g_free(hcurrent->data);
357                         hcurrent->data = NULL;
358                         *history = g_list_delete_link(*history, hcurrent);
359                 }
361                 while (g_list_length(*history) > wrln_max_history_length) {
362                         GList *first = g_list_first(*history);
364                         /* remove the oldest history entry  */
365                         g_free(first->data);
366                         first->data = NULL;
367                         *history = g_list_delete_link(*history, first);
368                 }
369         }
371         return g_strdup(wr.line);
374 gchar *
375 wreadln(WINDOW *w,
376         const gchar *prompt,
377         const gchar *initial_value,
378         unsigned x1,
379         GList **history,
380         GCompletion *gcmp)
382         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
385 gchar *
386 wreadln_masked(WINDOW *w,
387                const gchar *prompt,
388                const gchar *initial_value,
389                unsigned x1,
390                GList **history,
391                GCompletion *gcmp)
393         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);