f10b8c23f96c125f890b7ddc187ae4415c869ac2
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;
101 }
103 /* draw line buffer and update cursor position */
104 static inline void drawline(const struct wreadln *wr)
105 {
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();
118 }
120 static void
121 wreadln_insert_byte(struct wreadln *wr, gint key)
122 {
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);
141 }
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)
153 {
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);
366 }
368 gchar *
369 wreadln(WINDOW *w,
370 const gchar *prompt,
371 const gchar *initial_value,
372 gint x1,
373 GList **history,
374 GCompletion *gcmp)
375 {
376 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
377 }
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)
386 {
387 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
388 }