42703a0165ebc66566b7bca43625cf46b7c095a7
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)
101 {
102 wr->cursor = strlen(wr->line);
103 if (wr->cursor >= wr->width)
104 wr->start = wr->cursor - wr->width + 1;
105 }
107 /* draw line buffer and update cursor position */
108 static inline void drawline(const struct wreadln *wr)
109 {
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();
122 }
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)
134 {
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);
367 }
369 gchar *
370 wreadln(WINDOW *w,
371 const gchar *prompt,
372 const gchar *initial_value,
373 gint x1,
374 GList **history,
375 GCompletion *gcmp)
376 {
377 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
378 }
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)
387 {
388 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
389 }