• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /****************************************************************************
2  * Copyright 2019,2020 Thomas E. Dickey                                     *
3  * Copyright 2003-2014,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 /*
30  * $Id: edit_field.c,v 1.31 2020/02/02 23:34:34 tom Exp $
31  *
32  * A wrapper for form_driver() which keeps track of the user's editing changes
33  * for each field, and makes the resulting length available as a
34  * null-terminated string in field_buffer(field,1).
35  *
36  * Thomas Dickey - 2003/4/26.
37  */
38 
39 #include <test.priv.h>
40 
41 #if USE_LIBFORM
42 
43 #include <edit_field.h>
44 #include <popup_msg.h>
45 
46 static struct {
47     int code;
48     int result;
49     const char *help;
50 } commands[] = {
51 
52     {
53 	CTRL('A'), REQ_NEXT_CHOICE, ""
54     },
55     {
56 	CTRL('B'), REQ_PREV_WORD, "go to previous word"
57     },
58     {
59 	CTRL('C'), REQ_CLR_EOL, "clear to end of line"
60     },
61     {
62 	CTRL('D'), REQ_DOWN_FIELD, "move downward to field"
63     },
64     {
65 	CTRL('E'), REQ_END_FIELD, "go to end of field"
66     },
67     {
68 	CTRL('F'), REQ_NEXT_PAGE, "go to next page"
69     },
70     {
71 	CTRL('G'), REQ_DEL_WORD, "delete current word"
72     },
73     {
74 	CTRL('H'), REQ_DEL_PREV, "delete previous character"
75     },
76     {
77 	CTRL('I'), REQ_INS_CHAR, "insert character"
78     },
79     {
80 	CTRL('K'), REQ_CLR_EOF, "clear to end of field"
81     },
82     {
83 	CTRL('L'), REQ_LEFT_FIELD, "go to field to left"
84     },
85     {
86 	CTRL('M'), REQ_NEW_LINE, "insert/overlay new line"
87     },
88     {
89 	CTRL('N'), REQ_NEXT_FIELD, "go to next field"
90     },
91     {
92 	CTRL('O'), REQ_INS_LINE, "insert blank line at cursor"
93     },
94     {
95 	CTRL('P'), REQ_PREV_FIELD, "go to previous field"
96     },
97     {
98 	CTRL('Q'), MY_QUIT, "exit form"
99     },
100     {
101 	CTRL('R'), REQ_RIGHT_FIELD, "go to field to right"
102     },
103     {
104 	CTRL('S'), REQ_BEG_FIELD, "go to beginning of field"
105     },
106     {
107 	CTRL('T'), MY_EDT_MODE, "toggle O_EDIT mode, clear field status",
108     },
109     {
110 	CTRL('U'), REQ_UP_FIELD, "move upward to field"
111     },
112     {
113 	CTRL('V'), REQ_DEL_CHAR, "delete character"
114     },
115     {
116 	CTRL('W'), REQ_NEXT_WORD, "go to next word"
117     },
118     {
119 	CTRL('X'), REQ_CLR_FIELD, "clear field"
120     },
121     {
122 	CTRL('Y'), REQ_DEL_LINE, "delete line"
123     },
124     {
125 	CTRL('Z'), REQ_PREV_CHOICE, ""
126     },
127     {
128 	CTRL('['), MY_QUIT, "exit form"
129     },
130     {
131 	CTRL(']'), MY_INS_MODE, "toggle REQ_INS_MODE/REQ_OVL_MODE",
132     },
133     {
134 	KEY_F(1), MY_HELP, "show this screen",
135     },
136     {
137 	KEY_BACKSPACE, REQ_DEL_PREV, "delete previous character"
138     },
139     {
140 	KEY_DOWN, REQ_DOWN_CHAR, "move down 1 character"
141     },
142     {
143 	KEY_END, REQ_LAST_FIELD, "go to last field"
144     },
145     {
146 	KEY_HOME, REQ_FIRST_FIELD, "go to first field"
147     },
148     {
149 	KEY_LEFT, REQ_LEFT_CHAR, "move left 1 character"
150     },
151     {
152 	KEY_LL, REQ_LAST_FIELD, "go to last field"
153     },
154     {
155 	KEY_NEXT, REQ_NEXT_FIELD, "go to next field"
156     },
157     {
158 	KEY_NPAGE, REQ_NEXT_PAGE, "go to next page"
159     },
160     {
161 	KEY_PPAGE, REQ_PREV_PAGE, "go to previous page"
162     },
163     {
164 	KEY_PREVIOUS, REQ_PREV_FIELD, "go to previous field"
165     },
166     {
167 	KEY_RIGHT, REQ_RIGHT_CHAR, "move right 1 character"
168     },
169     {
170 	KEY_UP, REQ_UP_CHAR, "move up 1 character"
171     }
172 };
173 
174 /*
175  * Display a temporary window listing the keystroke-commands we recognize.
176  */
177 void
help_edit_field(void)178 help_edit_field(void)
179 {
180     int used = 0;
181     unsigned n;
182     char **msgs = typeCalloc(char *, 3 + SIZEOF(commands));
183 
184     msgs[used++] = strdup("Defined form edit/traversal keys:");
185     for (n = 0; n < SIZEOF(commands); ++n) {
186 	char *msg;
187 	const char *name;
188 	const char *code = keyname(commands[n].code);
189 	size_t need = 5;
190 #ifdef NCURSES_VERSION
191 	if ((name = form_request_name(commands[n].result)) == 0)
192 #endif
193 	    name = commands[n].help;
194 	need = 5 + strlen(code) + strlen(name);
195 	msg = typeMalloc(char, need);
196 	_nc_SPRINTF(msg, _nc_SLIMIT(need) "%s -- %s", code, name);
197 	msgs[used++] = msg;
198     }
199     msgs[used++] =
200 	strdup("Arrow keys move within a field as you would expect.");
201     msgs[used] = 0;
202     popup_msg2(stdscr, msgs);
203     for (n = 0; msgs[n] != 0; ++n) {
204 	free(msgs[n]);
205     }
206     free(msgs);
207 }
208 
209 static int
offset_in_field(FORM * form)210 offset_in_field(FORM *form)
211 {
212     FIELD *field = current_field(form);
213     int currow, curcol;
214 
215     form_getyx(form, currow, curcol);
216     return curcol + currow * (int) field->dcols;
217 }
218 
219 static void
inactive_field(FIELD * f)220 inactive_field(FIELD *f)
221 {
222     set_field_back(f, field_attrs(f)->background);
223 }
224 
225 FieldAttrs *
field_attrs(FIELD * f)226 field_attrs(FIELD *f)
227 {
228     return (FieldAttrs *) field_userptr(f);
229 }
230 
231 static int
buffer_length(FIELD * f)232 buffer_length(FIELD *f)
233 {
234     return field_attrs(f)->row_lengths[0];
235 }
236 
237 static void
set_buffer_length(FIELD * f,int length)238 set_buffer_length(FIELD *f, int length)
239 {
240     field_attrs(f)->row_lengths[0] = length;
241 }
242 
243 /*
244  * The userptr is used in edit_field.c's inactive_field(), as well as for
245  * keeping track of the actual lengths of lines in a multiline field.
246  */
247 void
init_edit_field(FIELD * f,char * value)248 init_edit_field(FIELD *f, char *value)
249 {
250     char empty[1];
251     FieldAttrs *ptr = field_attrs(f);
252     if (ptr == 0) {
253 	int rows, cols, frow, fcol, nrow, nbuf;
254 
255 	ptr = typeCalloc(FieldAttrs, (size_t) 1);
256 	ptr->background = field_back(f);
257 	if (field_info(f, &rows, &cols, &frow, &fcol, &nrow, &nbuf) == E_OK) {
258 	    ptr->row_count = nrow;
259 	    ptr->row_lengths = typeCalloc(int, (size_t) nrow + 1);
260 	}
261     }
262     if (value == 0) {
263 	value = empty;
264 	*value = '\0';
265     }
266     set_field_userptr(f, (void *) ptr);
267     set_field_buffer(f, 0, value);	/* will be formatted */
268     set_field_buffer(f, 1, value);	/* will be unformatted */
269     set_buffer_length(f, (int) strlen(value));
270 }
271 
272 int
edit_field(FORM * form,int * result)273 edit_field(FORM *form, int *result)
274 {
275     int ch = wgetch(form_win(form));
276     int status;
277     FIELD *before;
278     unsigned n;
279     int length;
280     int before_row;
281     int before_col;
282     int before_off = offset_in_field(form);
283 
284     form_getyx(form, before_row, before_col);
285     before = current_field(form);
286     set_field_back(before, A_NORMAL);
287     if (ch <= KEY_MAX) {
288 	set_field_back(before, A_REVERSE);
289     } else if (ch <= MAX_FORM_COMMAND) {
290 	inactive_field(before);
291     }
292 
293     *result = ch;
294     for (n = 0; n < SIZEOF(commands); ++n) {
295 	if (commands[n].code == ch) {
296 	    *result = commands[n].result;
297 	    break;
298 	}
299     }
300 
301     status = form_driver(form, *result);
302 
303     if (status == E_OK) {
304 	bool modified = TRUE;
305 
306 	length = buffer_length(before);
307 	if (length < before_off)
308 	    length = before_off;
309 	switch (*result) {
310 	case REQ_CLR_EOF:
311 	    length = before_off;
312 	    break;
313 	case REQ_CLR_EOL:
314 	    if ((int) (before_row + 1) == (int) (before->rows))
315 		length = before_off;
316 	    break;
317 	case REQ_CLR_FIELD:
318 	    length = 0;
319 	    break;
320 	case REQ_DEL_CHAR:
321 	    if (length > before_off)
322 		--length;
323 	    break;
324 	case REQ_DEL_PREV:
325 	    if (length > 0) {
326 		if (before_col > 0) {
327 		    --length;
328 		} else if (before_row > 0) {
329 		    length -= (int) before->cols + before_col;
330 		}
331 	    }
332 	    break;
333 	case REQ_NEW_LINE:
334 	    length += (int) before->cols;
335 	    break;
336 #if 0
337 	    /* FIXME: finish these */
338 	case REQ_DEL_LINE:	/* delete line */
339 	case REQ_DEL_WORD:	/* delete word at cursor */
340 	case REQ_INS_CHAR:	/* insert blank char at cursor */
341 	case REQ_INS_LINE:	/* insert blank line at cursor */
342 	case REQ_INS_MODE:	/* begin insert mode */
343 	case REQ_OVL_MODE:	/* begin overlay mode */
344 #endif
345 	    /* ignore all of the motion commands */
346 	case REQ_SCR_BCHAR:	/* FALLTHRU */
347 	case REQ_SCR_BHPAGE:	/* FALLTHRU */
348 	case REQ_SCR_BLINE:	/* FALLTHRU */
349 	case REQ_SCR_BPAGE:	/* FALLTHRU */
350 	case REQ_SCR_FCHAR:	/* FALLTHRU */
351 	case REQ_SCR_FHPAGE:	/* FALLTHRU */
352 	case REQ_SCR_FLINE:	/* FALLTHRU */
353 	case REQ_SCR_FPAGE:	/* FALLTHRU */
354 	case REQ_SCR_HBHALF:	/* FALLTHRU */
355 	case REQ_SCR_HBLINE:	/* FALLTHRU */
356 	case REQ_SCR_HFHALF:	/* FALLTHRU */
357 	case REQ_SCR_HFLINE:	/* FALLTHRU */
358 	case REQ_BEG_FIELD:	/* FALLTHRU */
359 	case REQ_BEG_LINE:	/* FALLTHRU */
360 	case REQ_DOWN_CHAR:	/* FALLTHRU */
361 	case REQ_DOWN_FIELD:	/* FALLTHRU */
362 	case REQ_END_FIELD:	/* FALLTHRU */
363 	case REQ_END_LINE:	/* FALLTHRU */
364 	case REQ_FIRST_FIELD:	/* FALLTHRU */
365 	case REQ_FIRST_PAGE:	/* FALLTHRU */
366 	case REQ_LAST_FIELD:	/* FALLTHRU */
367 	case REQ_LAST_PAGE:	/* FALLTHRU */
368 	case REQ_LEFT_CHAR:	/* FALLTHRU */
369 	case REQ_LEFT_FIELD:	/* FALLTHRU */
370 	case REQ_NEXT_CHAR:	/* FALLTHRU */
371 	case REQ_NEXT_CHOICE:	/* FALLTHRU */
372 	case REQ_NEXT_FIELD:	/* FALLTHRU */
373 	case REQ_NEXT_LINE:	/* FALLTHRU */
374 	case REQ_NEXT_PAGE:	/* FALLTHRU */
375 	case REQ_NEXT_WORD:	/* FALLTHRU */
376 	case REQ_PREV_CHAR:	/* FALLTHRU */
377 	case REQ_PREV_CHOICE:	/* FALLTHRU */
378 	case REQ_PREV_FIELD:	/* FALLTHRU */
379 	case REQ_PREV_LINE:	/* FALLTHRU */
380 	case REQ_PREV_PAGE:	/* FALLTHRU */
381 	case REQ_PREV_WORD:	/* FALLTHRU */
382 	case REQ_RIGHT_CHAR:	/* FALLTHRU */
383 	case REQ_RIGHT_FIELD:	/* FALLTHRU */
384 	case REQ_SFIRST_FIELD:	/* FALLTHRU */
385 	case REQ_SLAST_FIELD:	/* FALLTHRU */
386 	case REQ_SNEXT_FIELD:	/* FALLTHRU */
387 	case REQ_SPREV_FIELD:	/* FALLTHRU */
388 	case REQ_UP_CHAR:	/* FALLTHRU */
389 	case REQ_UP_FIELD:	/* FALLTHRU */
390 	case REQ_VALIDATION:	/* FALLTHRU */
391 	    modified = FALSE;
392 	    break;
393 
394 	default:
395 	    modified = FALSE;
396 	    if (ch >= MIN_FORM_COMMAND) {
397 		beep();
398 	    } else if (isprint(ch)) {
399 		modified = TRUE;
400 	    }
401 	    break;
402 	}
403 
404 	/*
405 	 * If we do not force a re-validation, then field_buffer 0 will
406 	 * be lagging by one character.
407 	 */
408 	if (modified && form_driver(form, REQ_VALIDATION) == E_OK && *result
409 	    < MIN_FORM_COMMAND)
410 	    ++length;
411 
412 	set_buffer_length(before, length);
413     }
414 
415     if (current_field(form) != before)
416 	inactive_field(before);
417     return status;
418 }
419 
420 void
free_edit_field(FIELD * f)421 free_edit_field(FIELD *f)
422 {
423     FieldAttrs *ptr = field_attrs(f);
424     if (ptr != 0) {
425 	free(ptr->row_lengths);
426 	free(ptr);
427     }
428 }
429 #else
430 
431 extern void no_edit_field(void);
432 
433 void
no_edit_field(void)434 no_edit_field(void)
435 {
436 }
437 
438 #endif
439