ncurses 6.2 - patch 20200418
[ncurses.git] / test / move_field.c
1 /****************************************************************************
2  * Copyright 2020 Thomas E. Dickey                                          *
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: move_field.c,v 1.6 2020/03/28 17:43:03 tom Exp $
30  *
31  * Demonstrate move_field().
32  */
33
34 #include <test.priv.h>
35
36 #if USE_LIBFORM
37
38 #include <edit_field.h>
39 #include <popup_msg.h>
40
41 #define DO_DEMO CTRL('F')       /* actual key for toggling demo-mode */
42 #define MY_DEMO EDIT_FIELD('f') /* internal request-code */
43
44 static char empty[] = "";
45 static FIELD *all_fields[100];
46
47 /* *INDENT-OFF* */
48 static struct {
49     int code;
50     int result;
51     const char *help;
52 } commands[] = {
53     { CTRL('A'),     REQ_BEG_FIELD,   "go to beginning of field" },
54     { CTRL('D'),     REQ_DOWN_FIELD,  "move downward to field" },
55     { CTRL('E'),     REQ_END_FIELD,   "go to end of field" },
56     { CTRL('H'),     REQ_DEL_PREV,    "delete previous character" },
57     { CTRL('I'),     REQ_NEXT_FIELD,  "go to next field" },
58     { CTRL('K'),     REQ_CLR_EOF,     "clear to end of field" },
59     { CTRL('N'),     REQ_NEXT_FIELD,  "go to next field" },
60     { CTRL('P'),     REQ_PREV_FIELD,  "go to previous field" },
61     { CTRL('Q'),     MY_QUIT,         "exit form" },
62     { CTRL('U'),     REQ_UP_FIELD,    "move upward to field" },
63     { CTRL('W'),     REQ_NEXT_WORD,   "go to next word" },
64     { CTRL('X'),     REQ_CLR_FIELD,   "clear field" },
65     { CTRL('['),     MY_QUIT,         "exit form" },
66     { KEY_F(1),      MY_HELP,         "show this screen", },
67     { KEY_BACKSPACE, REQ_DEL_PREV,    "delete previous character" },
68     { KEY_BTAB,      REQ_PREV_FIELD,  "go to previous field" },
69     { KEY_DOWN,      REQ_DOWN_CHAR,   "move down 1 character" },
70     { KEY_END,       REQ_LAST_FIELD,  "go to last field" },
71     { KEY_HOME,      REQ_FIRST_FIELD, "go to first field" },
72     { KEY_LEFT,      REQ_LEFT_CHAR,   "move left 1 character" },
73     { KEY_NEXT,      REQ_NEXT_FIELD,  "go to next field" },
74     { KEY_PREVIOUS,  REQ_PREV_FIELD,  "go to previous field" },
75     { KEY_RIGHT,     REQ_RIGHT_CHAR,  "move right 1 character" },
76     { KEY_UP,        REQ_UP_CHAR,     "move up 1 character" },
77     { DO_DEMO,       MY_DEMO,         "move current field with cursor keys" }
78 };
79 /* *INDENT-ON* */
80
81 static void
82 my_help_edit_field(void)
83 {
84     int used = 0;
85     unsigned n;
86     char **msgs = typeCalloc(char *, 3 + SIZEOF(commands));
87
88     msgs[used++] = strdup("Defined form edit/traversal keys:");
89     for (n = 0; n < SIZEOF(commands); ++n) {
90         char *msg;
91         const char *name;
92         const char *code = keyname(commands[n].code);
93         size_t need = 5;
94 #ifdef NCURSES_VERSION
95         if ((name = form_request_name(commands[n].result)) == 0)
96 #endif
97             name = commands[n].help;
98         need = 5 + strlen(code) + strlen(name);
99         msg = typeMalloc(char, need);
100         _nc_SPRINTF(msg, _nc_SLIMIT(need) "%s -- %s", code, name);
101         msgs[used++] = msg;
102     }
103     msgs[used++] =
104         strdup("Arrow keys move within a field as you would expect.");
105     msgs[used] = 0;
106     popup_msg2(stdscr, msgs);
107     for (n = 0; msgs[n] != 0; ++n) {
108         free(msgs[n]);
109     }
110     free(msgs);
111 }
112
113 static FIELD *
114 make_label(const char *label, int frow, int fcol)
115 {
116     FIELD *f = new_field(1, (int) strlen(label), frow, fcol, 0, 0);
117
118     if (f) {
119         set_field_buffer(f, 0, label);
120         set_field_opts(f, (int) ((unsigned) field_opts(f) & ~O_ACTIVE));
121     }
122     return (f);
123 }
124
125 static FIELD *
126 make_field(int frow, int fcol, int rows, int cols)
127 {
128     FIELD *f = new_field(rows, cols, frow, fcol, 0, 1);
129
130     if (f) {
131         set_field_back(f, A_UNDERLINE);
132         init_edit_field(f, empty);
133     }
134     return (f);
135 }
136
137 static void
138 erase_form(FORM *f)
139 {
140     WINDOW *w = form_win(f);
141     WINDOW *s = form_sub(f);
142
143     unpost_form(f);
144     werase(w);
145     wrefresh(w);
146     delwin(s);
147     delwin(w);
148 }
149
150 static FieldAttrs *
151 my_field_attrs(FIELD *f)
152 {
153     return (FieldAttrs *) field_userptr(f);
154 }
155
156 static int
157 buffer_length(FIELD *f)
158 {
159     return my_field_attrs(f)->row_lengths[0];
160 }
161
162 static void
163 set_buffer_length(FIELD *f, int length)
164 {
165     my_field_attrs(f)->row_lengths[0] = length;
166 }
167
168 static int
169 offset_in_field(FORM *form)
170 {
171     FIELD *field = current_field(form);
172     int currow, curcol;
173
174     form_getyx(form, currow, curcol);
175     return curcol + currow * (int) field->dcols;
176 }
177
178 static void
179 inactive_field(FIELD *f)
180 {
181     set_field_back(f, my_field_attrs(f)->background);
182 }
183
184 static int
185 my_edit_field(FORM *form, int *result)
186 {
187     int ch = wgetch(form_win(form));
188     int status;
189     FIELD *before;
190     unsigned n;
191     int length;
192     int before_row;
193     int before_col;
194     int before_off = offset_in_field(form);
195
196     form_getyx(form, before_row, before_col);
197     before = current_field(form);
198     set_field_back(before, A_NORMAL);
199     if (ch <= KEY_MAX) {
200         set_field_back(before, A_REVERSE);
201     } else if (ch <= MAX_FORM_COMMAND) {
202         inactive_field(before);
203     }
204
205     *result = ch;
206     for (n = 0; n < SIZEOF(commands); ++n) {
207         if (commands[n].code == ch) {
208             *result = commands[n].result;
209             break;
210         }
211     }
212
213     status = form_driver(form, *result);
214
215     if (status == E_OK) {
216         bool modified = TRUE;
217
218         length = buffer_length(before);
219         if (length < before_off)
220             length = before_off;
221         switch (*result) {
222         case REQ_CLR_EOF:
223             length = before_off;
224             break;
225         case REQ_CLR_EOL:
226             if ((int) (before_row + 1) == (int) (before->rows))
227                 length = before_off;
228             break;
229         case REQ_CLR_FIELD:
230             length = 0;
231             break;
232         case REQ_DEL_CHAR:
233             if (length > before_off)
234                 --length;
235             break;
236         case REQ_DEL_PREV:
237             if (length > 0) {
238                 if (before_col > 0) {
239                     --length;
240                 } else if (before_row > 0) {
241                     length -= (int) before->cols + before_col;
242                 }
243             }
244             break;
245         case REQ_NEW_LINE:
246             length += (int) before->cols;
247             break;
248
249         default:
250             modified = (ch < MIN_FORM_COMMAND
251                         && isprint(ch));
252             break;
253         }
254
255         /*
256          * If we do not force a re-validation, then field_buffer 0 will
257          * be lagging by one character.
258          */
259         if (modified && form_driver(form, REQ_VALIDATION) == E_OK && *result
260             < MIN_FORM_COMMAND)
261             ++length;
262
263         set_buffer_length(before, length);
264     }
265
266     if (current_field(form) != before)
267         inactive_field(before);
268     return status;
269 }
270
271 static FIELD **
272 copy_fields(FIELD **source, size_t length)
273 {
274     FIELD **target = calloc(length + 1, sizeof(FIELD *));
275     memcpy(target, source, length * sizeof(FIELD *));
276     return target;
277 }
278
279 /* display a status message to show what's happening */
280 static void
281 show_status(FORM *form, FIELD *field)
282 {
283     WINDOW *sub = form_sub(form);
284     int currow, curcol;
285
286     getyx(stdscr, currow, curcol);
287     mvprintw(LINES - 1, 0,
288              "Field at [%d,%d].  Press %s to quit moving.",
289              getbegy(sub) + field->frow,
290              getbegx(sub) + field->fcol,
291              keyname(DO_DEMO));
292     clrtobot();
293     move(currow, curcol);
294     refresh();
295 }
296
297 /*
298  * Move the current label+field in response to cursor-keys (or h,j,k,l) until
299  * a control/F is read.
300  */
301 static void
302 do_demo(FORM *form)
303 {
304     int count = field_count(form);
305     FIELD *my_field = current_field(form);
306
307     if (count > 0 && my_field != NULL) {
308         size_t needed = (size_t) count;
309         FIELD **old_fields = copy_fields(form_fields(form), needed);
310         FIELD **new_fields = copy_fields(form_fields(form), needed);
311         int ch;
312
313         if (old_fields != NULL && new_fields != NULL) {
314             bool found = FALSE;
315
316             /* TODO: move the label too, in parallel with the editing field */
317
318             /* remove the current field from the newer list */
319             for (ch = 0; ch <= count; ++ch) {
320                 if (found) {
321                     new_fields[ch - 1] = new_fields[ch];
322                 } else if (new_fields[ch] == my_field) {
323                     found = TRUE;
324                 }
325             }
326
327             if (found) {
328                 int currow, curcol;
329
330                 getyx(stdscr, currow, curcol);
331
332                 show_status(form, my_field);
333                 ch = '?';
334                 while ((ch = wgetch(form_win(form))) != DO_DEMO) {
335                     int field_y = my_field->frow;
336                     int field_x = my_field->fcol;
337
338                     switch (ch) {
339                     case 'h':
340                     case KEY_LEFT:
341                         if (field_x > 0)
342                             field_x--;
343                         break;
344                     case 'j':
345                     case KEY_DOWN:
346                         field_y++;
347                         break;
348                     case 'k':
349                     case KEY_UP:
350                         if (field_y > 0)
351                             field_y--;
352                         break;
353                     case 'l':
354                     case KEY_RIGHT:
355                         field_x++;
356                         break;
357                     case CTRL('Q'):
358                     case CTRL('['):
359                         ch = DO_DEMO;
360                         /* FALLTHRU */
361                     case DO_DEMO:
362                         break;
363                     default:
364                         continue;
365                     }
366
367                     if (ch == DO_DEMO)
368                         break;
369
370                     /* alter connected fields temporarily to move the field */
371                     unpost_form(form);
372                     set_form_fields(form, new_fields);
373                     post_form(form);
374
375                     /* TODO: update screen position on success */
376                     move_field(my_field, field_y, field_x);
377
378                     /* restore the form's list of fields */
379                     unpost_form(form);
380                     set_form_fields(form, old_fields);
381                     post_form(form);
382
383                     show_status(form, my_field);
384                 }
385
386                 /* cleanup */
387                 move(LINES - 1, 0);
388                 clrtobot();
389                 move(currow, curcol);
390                 refresh();
391             }
392         }
393         free(new_fields);
394     }
395 }
396
397 static int
398 my_form_driver(FORM *form, int c)
399 {
400     switch (c) {
401     case MY_QUIT:
402         if (form_driver(form, REQ_VALIDATION) == E_OK)
403             return (TRUE);
404         break;
405     case MY_HELP:
406         my_help_edit_field();
407         break;
408     case MY_DEMO:
409         do_demo(form);
410         break;
411     default:
412         beep();
413         break;
414     }
415     return (FALSE);
416 }
417
418 static void
419 demo_forms(void)
420 {
421     FORM *form;
422     int c;
423     unsigned n = 0;
424     const char *fname;
425
426     /* describe the form */
427     all_fields[n++] = make_label("Sample Form", 0, 15);
428
429     fname = "Last Name";
430     all_fields[n++] = make_label(fname, 2, 0);
431     all_fields[n++] = make_field(3, 0, 1, 18);
432     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
433
434     fname = "First Name";
435     all_fields[n++] = make_label(fname, 2, 20);
436     all_fields[n++] = make_field(3, 20, 1, 12);
437     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
438
439     fname = "Middle Name";
440     all_fields[n++] = make_label(fname, 2, 34);
441     all_fields[n++] = make_field(3, 34, 1, 12);
442     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
443
444     fname = "Comments";
445     all_fields[n++] = make_label(fname, 5, 0);
446     all_fields[n++] = make_field(6, 0, 4, 46);
447     init_edit_field(all_fields[n - 1], empty);
448
449     all_fields[n] = (FIELD *) 0;
450
451     if ((form = new_form(all_fields)) != 0) {
452         int finished = 0;
453
454         post_form(form);
455
456         while (!finished) {
457             switch (my_edit_field(form, &c)) {
458             case E_OK:
459                 break;
460             case E_UNKNOWN_COMMAND:
461                 finished = my_form_driver(form, c);
462                 break;
463             default:
464                 beep();
465                 break;
466             }
467         }
468
469         erase_form(form);
470
471         free_form(form);
472     }
473     for (c = 0; all_fields[c] != 0; c++) {
474         free_edit_field(all_fields[c]);
475         free_field(all_fields[c]);
476     }
477     noraw();
478     nl();
479 }
480
481 int
482 main(void)
483 {
484     setlocale(LC_ALL, "");
485
486     initscr();
487     cbreak();
488     noecho();
489     raw();
490     nonl();                     /* lets us read ^M's */
491     intrflush(stdscr, FALSE);
492     keypad(stdscr, TRUE);
493
494     if (has_colors()) {
495         start_color();
496         init_pair(1, COLOR_WHITE, COLOR_BLUE);
497         init_pair(2, COLOR_GREEN, COLOR_BLACK);
498         init_pair(3, COLOR_CYAN, COLOR_BLACK);
499         bkgd((chtype) COLOR_PAIR(1));
500         refresh();
501     }
502
503     demo_forms();
504
505     endwin();
506     ExitProgram(EXIT_SUCCESS);
507 }
508
509 #else
510 int
511 main(void)
512 {
513     printf("This program requires the curses form library\n");
514     ExitProgram(EXIT_FAILURE);
515 }
516 #endif