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)
100 {
101 wr->cursor = strlen(wr->line);
102 if (wr->cursor >= wr->width)
103 wr->start = wr->cursor - wr->width + 1;
104 }
106 /* draw line buffer and update cursor position */
107 static inline void drawline(const struct wreadln *wr)
108 {
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();
121 }
123 static void
124 wreadln_insert_byte(struct wreadln *wr, gint key)
125 {
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;
137 }
139 static void
140 wreadln_delete_char(struct wreadln *wr, size_t x)
141 {
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);
149 }
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)
161 {
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);
372 }
374 gchar *
375 wreadln(WINDOW *w,
376 const gchar *prompt,
377 const gchar *initial_value,
378 unsigned x1,
379 GList **history,
380 GCompletion *gcmp)
381 {
382 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
383 }
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)
392 {
393 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
394 }