]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/move_field.c
7681225bf9882d8c1e97c0f0ed512de14c929f98
[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.5 2020/03/21 22:04: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, int 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         FIELD **old_fields = copy_fields(form_fields(form), count);
309         FIELD **new_fields = copy_fields(form_fields(form), count);
310         int ch;
311
312         if (old_fields != NULL && new_fields != NULL) {
313             bool found = FALSE;
314
315             /* TODO: move the label too, in parallel with the editing field */
316
317             /* remove the current field from the newer list */
318             for (ch = 0; ch <= count; ++ch) {
319                 if (found) {
320                     new_fields[ch - 1] = new_fields[ch];
321                 } else if (new_fields[ch] == my_field) {
322                     found = TRUE;
323                 }
324             }
325
326             if (found) {
327                 int currow, curcol;
328
329                 getyx(stdscr, currow, curcol);
330
331                 show_status(form, my_field);
332                 ch = '?';
333                 while ((ch = wgetch(form_win(form))) != DO_DEMO) {
334                     int field_y = my_field->frow;
335                     int field_x = my_field->fcol;
336
337                     switch (ch) {
338                     case 'h':
339                     case KEY_LEFT:
340                         if (field_x > 0)
341                             field_x--;
342                         break;
343                     case 'j':
344                     case KEY_DOWN:
345                         field_y++;
346                         break;
347                     case 'k':
348                     case KEY_UP:
349                         if (field_y > 0)
350                             field_y--;
351                         break;
352                     case 'l':
353                     case KEY_RIGHT:
354                         field_x++;
355                         break;
356                     case CTRL('Q'):
357                     case CTRL('['):
358                         ch = DO_DEMO;
359                         /* FALLTHRU */
360                     case DO_DEMO:
361                         break;
362                     default:
363                         continue;
364                     }
365
366                     if (ch == DO_DEMO)
367                         break;
368
369                     /* alter connected fields temporarily to move the field */
370                     unpost_form(form);
371                     set_form_fields(form, new_fields);
372                     post_form(form);
373
374                     /* TODO: update screen position on success */
375                     move_field(my_field, field_y, field_x);
376
377                     /* restore the form's list of fields */
378                     unpost_form(form);
379                     set_form_fields(form, old_fields);
380                     post_form(form);
381
382                     show_status(form, my_field);
383                 }
384
385                 /* cleanup */
386                 move(LINES - 1, 0);
387                 clrtobot();
388                 move(currow, curcol);
389                 refresh();
390             }
391         }
392         free(new_fields);
393     }
394 }
395
396 static int
397 my_form_driver(FORM *form, int c)
398 {
399     switch (c) {
400     case MY_QUIT:
401         if (form_driver(form, REQ_VALIDATION) == E_OK)
402             return (TRUE);
403         break;
404     case MY_HELP:
405         my_help_edit_field();
406         break;
407     case MY_DEMO:
408         do_demo(form);
409         break;
410     default:
411         beep();
412         break;
413     }
414     return (FALSE);
415 }
416
417 static void
418 demo_forms(void)
419 {
420     FORM *form;
421     int c;
422     unsigned n = 0;
423     const char *fname;
424
425     /* describe the form */
426     all_fields[n++] = make_label("Sample Form", 0, 15);
427
428     fname = "Last Name";
429     all_fields[n++] = make_label(fname, 2, 0);
430     all_fields[n++] = make_field(3, 0, 1, 18);
431     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
432
433     fname = "First Name";
434     all_fields[n++] = make_label(fname, 2, 20);
435     all_fields[n++] = make_field(3, 20, 1, 12);
436     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
437
438     fname = "Middle Name";
439     all_fields[n++] = make_label(fname, 2, 34);
440     all_fields[n++] = make_field(3, 34, 1, 12);
441     set_field_type(all_fields[n - 1], TYPE_ALPHA, 1);
442
443     fname = "Comments";
444     all_fields[n++] = make_label(fname, 5, 0);
445     all_fields[n++] = make_field(6, 0, 4, 46);
446     init_edit_field(all_fields[n - 1], empty);
447
448     all_fields[n] = (FIELD *) 0;
449
450     if ((form = new_form(all_fields)) != 0) {
451         int finished = 0;
452
453         post_form(form);
454
455         while (!finished) {
456             switch (my_edit_field(form, &c)) {
457             case E_OK:
458                 break;
459             case E_UNKNOWN_COMMAND:
460                 finished = my_form_driver(form, c);
461                 break;
462             default:
463                 beep();
464                 break;
465             }
466         }
467
468         erase_form(form);
469
470         free_form(form);
471     }
472     for (c = 0; all_fields[c] != 0; c++) {
473         free_edit_field(all_fields[c]);
474         free_field(all_fields[c]);
475     }
476     noraw();
477     nl();
478 }
479
480 int
481 main(void)
482 {
483     setlocale(LC_ALL, "");
484
485     initscr();
486     cbreak();
487     noecho();
488     raw();
489     nonl();                     /* lets us read ^M's */
490     intrflush(stdscr, FALSE);
491     keypad(stdscr, TRUE);
492
493     if (has_colors()) {
494         start_color();
495         init_pair(1, COLOR_WHITE, COLOR_BLUE);
496         init_pair(2, COLOR_GREEN, COLOR_BLACK);
497         init_pair(3, COLOR_CYAN, COLOR_BLACK);
498         bkgd((chtype) COLOR_PAIR(1));
499         refresh();
500     }
501
502     demo_forms();
503
504     endwin();
505     ExitProgram(EXIT_SUCCESS);
506 }
507
508 #else
509 int
510 main(void)
511 {
512     printf("This program requires the curses form library\n");
513     ExitProgram(EXIT_FAILURE);
514 }
515 #endif