Code

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