Code

wreadln: moved code to wreadln_delete()
[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         cursor_move_right(wr);
136 static void
137 wreadln_delete_char(struct wreadln *wr, size_t x)
139         size_t i;
141         assert(x < strlen(wr->line));
143         for (i = x; wr->line[i] != 0; i++)
144                 wr->line[i] = wr->line[i + 1];
147 /* libcurses version */
149 static gchar *
150 _wreadln(WINDOW *w,
151          const gchar *prompt,
152          const gchar *initial_value,
153          unsigned x1,
154          GList **history,
155          GCompletion *gcmp,
156          gboolean masked)
158         struct wreadln wr = {
159                 .w = w,
160                 .masked = masked,
161                 .cursor = 0,
162                 .start = 0,
163         };
164         GList *hlist = NULL, *hcurrent = NULL;
165         gint key = 0;
166         size_t i;
168         /* turn off echo */
169         noecho();
170         /* make shure the cursor is visible */
171         curs_set(1);
172         /* print prompt string */
173         if (prompt)
174                 waddstr(w, prompt);
175         /* retrive y and x0 position */
176         getyx(w, wr.y, wr.x);
177         /* check the x1 value */
178         if (x1 <= wr.x || x1 > (unsigned)COLS)
179                 x1 = COLS;
180         wr.width = x1 - wr.x;
181         /* clear input area */
182         mvwhline(w, wr.y, wr.x, ' ', wr.width);
184         if (history) {
185                 /* append the a new line to our history list */
186                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
187                 /* hlist points to the current item in the history list */
188                 hlist = g_list_last(*history);
189                 hcurrent = hlist;
190         }
192         if (initial_value == (char *)-1) {
193                 /* get previous history entry */
194                 if (history && hlist->prev) {
195                         if (hlist == hcurrent)
196                                 /* save the current line */
197                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
199                         /* get previous line */
200                         hlist = hlist->prev;
201                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
202                 }
203                 cursor_move_to_eol(&wr);
204                 drawline(&wr);
205         } else if (initial_value) {
206                 /* copy the initial value to the line buffer */
207                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
208                 cursor_move_to_eol(&wr);
209                 drawline(&wr);
210         }
212         while (key != 13 && key != '\n') {
213                 key = wgetch(w);
215                 /* check if key is a function key */
216                 for (i = 0; i < 63; i++)
217                         if (key == (int)KEY_F(i)) {
218                                 key = KEY_F(1);
219                                 i = 64;
220                         }
222                 switch (key) {
223 #ifdef HAVE_GETMOUSE
224                 case KEY_MOUSE: /* ignore mouse events */
225 #endif
226                 case ERR: /* ingnore errors */
227                         break;
229                 case TAB:
230                         if (gcmp) {
231                                 char *prefix = NULL;
232                                 GList *list;
234                                 if (wrln_pre_completion_callback)
235                                         wrln_pre_completion_callback(gcmp, wr.line,
236                                                                      wrln_completion_callback_data);
237                                 list = g_completion_complete(gcmp, wr.line, &prefix);
238                                 if (prefix) {
239                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
240                                         cursor_move_to_eol(&wr);
241                                         g_free(prefix);
242                                 } else
243                                         screen_bell();
245                                 if (wrln_post_completion_callback)
246                                         wrln_post_completion_callback(gcmp, wr.line, list,
247                                                                       wrln_completion_callback_data);
248                         }
249                         break;
251                 case KEY_CTRL_G:
252                         screen_bell();
253                         if (history) {
254                                 g_free(hcurrent->data);
255                                 hcurrent->data = NULL;
256                                 *history = g_list_delete_link(*history, hcurrent);
257                         }
258                         return NULL;
260                 case KEY_LEFT:
261                 case KEY_CTRL_B:
262                         cursor_move_left(&wr);
263                         break;
264                 case KEY_RIGHT:
265                 case KEY_CTRL_F:
266                         cursor_move_right(&wr);
267                         break;
268                 case KEY_HOME:
269                 case KEY_CTRL_A:
270                         wr.cursor = 0;
271                         wr.start = 0;
272                         break;
273                 case KEY_END:
274                 case KEY_CTRL_E:
275                         cursor_move_to_eol(&wr);
276                         break;
277                 case KEY_CTRL_K:
278                         wr.line[wr.cursor] = 0;
279                         break;
280                 case KEY_CTRL_U:
281                         wr.cursor = utf8_width(wr.line);
282                         for (i = 0; i < wr.cursor; i++)
283                                 wr.line[i] = '\0';
284                         wr.cursor = 0;
285                         break;
286                 case 127:
287                 case KEY_BCKSPC:        /* handle backspace: copy all */
288                 case KEY_BACKSPACE:     /* chars starting from curpos */
289                         if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf   */
290                                 cursor_move_left(&wr);
291                                 wreadln_delete_char(&wr, wr.cursor);
292                         }
293                         break;
294                 case KEY_DC:            /* handle delete key. As above */
295                 case KEY_CTRL_D:
296                         if (wr.line[wr.cursor] != 0)
297                                 wreadln_delete_char(&wr, wr.cursor);
298                         break;
299                 case KEY_UP:
300                 case KEY_CTRL_P:
301                         /* get previous history entry */
302                         if (history && hlist->prev) {
303                                 if (hlist == hcurrent)
304                                         /* save the current line */
305                                         g_strlcpy(hlist->data, wr.line,
306                                                   sizeof(wr.line));
308                                 /* get previous line */
309                                 hlist = hlist->prev;
310                                 g_strlcpy(wr.line, hlist->data,
311                                           sizeof(wr.line));
312                         }
313                         cursor_move_to_eol(&wr);
314                         break;
315                 case KEY_DOWN:
316                 case KEY_CTRL_N:
317                         /* get next history entry */
318                         if (history && hlist->next) {
319                                 /* get next line */
320                                 hlist = hlist->next;
321                                 g_strlcpy(wr.line, hlist->data,
322                                           sizeof(wr.line));
323                         }
324                         cursor_move_to_eol(&wr);
325                         break;
327                 case '\n':
328                 case 13:
329                 case KEY_IC:
330                 case KEY_PPAGE:
331                 case KEY_NPAGE:
332                 case KEY_F(1):
333                         /* ignore char */
334                         break;
335                 default:
336                         if (key >= 32)
337                                 wreadln_insert_byte(&wr, key);
338                 }
340                 drawline(&wr);
341         }
343         /* update history */
344         if (history) {
345                 if (strlen(wr.line)) {
346                         /* update the current history entry */
347                         size_t size = strlen(wr.line) + 1;
348                         hcurrent->data = g_realloc(hcurrent->data, size);
349                         g_strlcpy(hcurrent->data, wr.line, size);
350                 } else {
351                         /* the line was empty - remove the current history entry */
352                         g_free(hcurrent->data);
353                         hcurrent->data = NULL;
354                         *history = g_list_delete_link(*history, hcurrent);
355                 }
357                 while (g_list_length(*history) > wrln_max_history_length) {
358                         GList *first = g_list_first(*history);
360                         /* remove the oldest history entry  */
361                         g_free(first->data);
362                         first->data = NULL;
363                         *history = g_list_delete_link(*history, first);
364                 }
365         }
367         return g_strdup(wr.line);
370 gchar *
371 wreadln(WINDOW *w,
372         const gchar *prompt,
373         const gchar *initial_value,
374         unsigned x1,
375         GList **history,
376         GCompletion *gcmp)
378         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
381 gchar *
382 wreadln_masked(WINDOW *w,
383                const gchar *prompt,
384                const gchar *initial_value,
385                unsigned x1,
386                GList **history,
387                GCompletion *gcmp)
389         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);