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