Code

wreadln: moved code to insert_byte()
[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         gint x, y;
50         /** the screen width of the input field */
51         gint width;
53         /** is the input masked, i.e. characters displayed as '*'? */
54         const gboolean masked;
56         /** the byte position of the cursor */
57         gint cursor;
59         /** the byte position displayed at the origin (for horizontal
60             scrolling) */
61         gint 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->cursor < (int)strlen(wr->line)) {
78                 ++wr->cursor;
79                 if (wr->cursor >= wr->width &&
80                     wr->start < wr->cursor - wr->width + 1)
81                         ++wr->start;
82         }
83 }
85 /* move the cursor one step to the left */
86 static inline void cursor_move_left(struct wreadln *wr)
87 {
88         if (wr->cursor > 0) {
89                 if (wr->cursor == wr->start && wr->start > 0)
90                         --wr->start;
91                 --wr->cursor;
92         }
93 }
95 /* move the cursor to the end of the line */
96 static inline void cursor_move_to_eol(struct wreadln *wr)
97 {
98         wr->cursor = strlen(wr->line);
99         if (wr->cursor >= wr->width)
100                 wr->start = wr->cursor - wr->width + 1;
103 /* draw line buffer and update cursor position */
104 static inline void drawline(const struct wreadln *wr)
106         wmove(wr->w, wr->y, wr->x);
107         /* clear input area */
108         whline(wr->w, ' ', wr->width);
109         /* print visible part of the line buffer */
110         if (wr->masked)
111                 whline(wr->w, '*', utf8_width(wr->line) - wr->start);
112         else
113                 waddnstr(wr->w, wr->line + wr->start, wr->width);
114         /* move the cursor to the correct position */
115         wmove(wr->w, wr->y, wr->x + wr->cursor - wr->start);
116         /* tell ncurses to redraw the screen */
117         doupdate();
120 static void
121 wreadln_insert_byte(struct wreadln *wr, gint key)
123         if (strlen(wr->line + wr->cursor)) { /* if the cursor is */
124                 /* not at the last pos */
125                 gchar *tmp = NULL;
126                 gsize rest = strlen(wr->line + wr->cursor) + 1;
128                 tmp = g_malloc0(rest);
129                 g_strlcpy (tmp, wr->line + wr->cursor, rest);
130                 wr->line[wr->cursor] = key;
131                 wr->line[wr->cursor + 1] = 0;
132                 g_strlcat(&wr->line[wr->cursor + 1], tmp, rest);
133                 g_free(tmp);
134                 cursor_move_right(wr);
135         } else {
136                 wr->line[wr->cursor + 1] = 0;
137                 wr->line[wr->cursor] = key;
138         }
140         cursor_move_right(wr);
143 /* libcurses version */
145 static gchar *
146 _wreadln(WINDOW *w,
147          const gchar *prompt,
148          const gchar *initial_value,
149          gint x1,
150          GList **history,
151          GCompletion *gcmp,
152          gboolean masked)
154         struct wreadln wr = {
155                 .w = w,
156                 .masked = masked,
157                 .cursor = 0,
158                 .start = 0,
159         };
160         GList *hlist = NULL, *hcurrent = NULL;
161         gint key = 0, i;
163         /* turn off echo */
164         noecho();
165         /* make shure the cursor is visible */
166         curs_set(1);
167         /* print prompt string */
168         if (prompt)
169                 waddstr(w, prompt);
170         /* retrive y and x0 position */
171         getyx(w, wr.y, wr.x);
172         /* check the x1 value */
173         if (x1 <= wr.x || x1 > COLS)
174                 x1 = COLS;
175         wr.width = x1 - wr.x;
176         /* clear input area */
177         mvwhline(w, wr.y, wr.x, ' ', wr.width);
179         if (history) {
180                 /* append the a new line to our history list */
181                 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
182                 /* hlist points to the current item in the history list */
183                 hlist = g_list_last(*history);
184                 hcurrent = hlist;
185         }
187         if (initial_value == (char *)-1) {
188                 /* get previous history entry */
189                 if (history && hlist->prev) {
190                         if (hlist == hcurrent)
191                                 /* save the current line */
192                                 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
194                         /* get previous line */
195                         hlist = hlist->prev;
196                         g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
197                 }
198                 cursor_move_to_eol(&wr);
199                 drawline(&wr);
200         } else if (initial_value) {
201                 /* copy the initial value to the line buffer */
202                 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
203                 cursor_move_to_eol(&wr);
204                 drawline(&wr);
205         }
207         while (key != 13 && key != '\n') {
208                 key = wgetch(w);
210                 /* check if key is a function key */
211                 for (i = 0; i < 63; i++)
212                         if (key == KEY_F(i)) {
213                                 key = KEY_F(1);
214                                 i = 64;
215                         }
217                 switch (key) {
218 #ifdef HAVE_GETMOUSE
219                 case KEY_MOUSE: /* ignore mouse events */
220 #endif
221                 case ERR: /* ingnore errors */
222                         break;
224                 case TAB:
225                         if (gcmp) {
226                                 char *prefix = NULL;
227                                 GList *list;
229                                 if (wrln_pre_completion_callback)
230                                         wrln_pre_completion_callback(gcmp, wr.line,
231                                                                      wrln_completion_callback_data);
232                                 list = g_completion_complete(gcmp, wr.line, &prefix);
233                                 if (prefix) {
234                                         g_strlcpy(wr.line, prefix, sizeof(wr.line));
235                                         cursor_move_to_eol(&wr);
236                                         g_free(prefix);
237                                 } else
238                                         screen_bell();
240                                 if (wrln_post_completion_callback)
241                                         wrln_post_completion_callback(gcmp, wr.line, list,
242                                                                       wrln_completion_callback_data);
243                         }
244                         break;
246                 case KEY_CTRL_G:
247                         screen_bell();
248                         if (history) {
249                                 g_free(hcurrent->data);
250                                 hcurrent->data = NULL;
251                                 *history = g_list_delete_link(*history, hcurrent);
252                         }
253                         return NULL;
255                 case KEY_LEFT:
256                 case KEY_CTRL_B:
257                         cursor_move_left(&wr);
258                         break;
259                 case KEY_RIGHT:
260                 case KEY_CTRL_F:
261                         cursor_move_right(&wr);
262                         break;
263                 case KEY_HOME:
264                 case KEY_CTRL_A:
265                         wr.cursor = 0;
266                         wr.start = 0;
267                         break;
268                 case KEY_END:
269                 case KEY_CTRL_E:
270                         cursor_move_to_eol(&wr);
271                         break;
272                 case KEY_CTRL_K:
273                         wr.line[wr.cursor] = 0;
274                         break;
275                 case KEY_CTRL_U:
276                         wr.cursor = utf8_width(wr.line);
277                         for (i = 0; i < wr.cursor; i++)
278                                 wr.line[i] = '\0';
279                         wr.cursor = 0;
280                         break;
281                 case 127:
282                 case KEY_BCKSPC:        /* handle backspace: copy all */
283                 case KEY_BACKSPACE:     /* chars starting from curpos */
284                         if (wr.cursor > 0) {/* - 1 from buf[n+1] to buf   */
285                                 for (i = wr.cursor - 1; wr.line[i] != 0; i++)
286                                         wr.line[i] = wr.line[i + 1];
287                                 cursor_move_left(&wr);
288                         }
289                         break;
290                 case KEY_DC:            /* handle delete key. As above */
291                 case KEY_CTRL_D:
292                         if (wr.cursor <= (gint)utf8_width(wr.line) - 1) {
293                                 for (i = wr.cursor; wr.line[i] != 0; i++)
294                                         wr.line[i] = wr.line[i + 1];
295                         }
296                         break;
297                 case KEY_UP:
298                 case KEY_CTRL_P:
299                         /* get previous history entry */
300                         if (history && hlist->prev) {
301                                 if (hlist == hcurrent)
302                                         /* save the current line */
303                                         g_strlcpy(hlist->data, wr.line,
304                                                   sizeof(wr.line));
306                                 /* get previous line */
307                                 hlist = hlist->prev;
308                                 g_strlcpy(wr.line, hlist->data,
309                                           sizeof(wr.line));
310                         }
311                         cursor_move_to_eol(&wr);
312                         break;
313                 case KEY_DOWN:
314                 case KEY_CTRL_N:
315                         /* get next history entry */
316                         if (history && hlist->next) {
317                                 /* get next line */
318                                 hlist = hlist->next;
319                                 g_strlcpy(wr.line, hlist->data,
320                                           sizeof(wr.line));
321                         }
322                         cursor_move_to_eol(&wr);
323                         break;
325                 case '\n':
326                 case 13:
327                 case KEY_IC:
328                 case KEY_PPAGE:
329                 case KEY_NPAGE:
330                 case KEY_F(1):
331                         /* ignore char */
332                         break;
333                 default:
334                         if (key >= 32)
335                                 wreadln_insert_byte(&wr, key);
336                 }
338                 drawline(&wr);
339         }
341         /* update history */
342         if (history) {
343                 if (strlen(wr.line)) {
344                         /* update the current history entry */
345                         size_t size = strlen(wr.line) + 1;
346                         hcurrent->data = g_realloc(hcurrent->data, size);
347                         g_strlcpy(hcurrent->data, wr.line, size);
348                 } else {
349                         /* the line was empty - remove the current history entry */
350                         g_free(hcurrent->data);
351                         hcurrent->data = NULL;
352                         *history = g_list_delete_link(*history, hcurrent);
353                 }
355                 while (g_list_length(*history) > wrln_max_history_length) {
356                         GList *first = g_list_first(*history);
358                         /* remove the oldest history entry  */
359                         g_free(first->data);
360                         first->data = NULL;
361                         *history = g_list_delete_link(*history, first);
362                 }
363         }
365         return g_strdup(wr.line);
368 gchar *
369 wreadln(WINDOW *w,
370         const gchar *prompt,
371         const gchar *initial_value,
372         gint x1,
373         GList **history,
374         GCompletion *gcmp)
376         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
379 gchar *
380 wreadln_masked(WINDOW *w,
381                const gchar *prompt,
382                const gchar *initial_value,
383                gint x1,
384                GList **history,
385                GCompletion *gcmp)
387         return  _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);