Code

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