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