C-Menu 0.2.9
A User Interface Toolkit
Loading...
Searching...
No Matches
form_engine.c
Go to the documentation of this file.
1/** @file form_engine.c
2 @brief The working part of C-Menu Form
3 @author Bill Waller
4 Copyright (c) 2025
5 MIT License
6 billxwaller@gmail.com
7 @date 2026-02-09
8 */
9
10/** @defgroup form_engine Form Engine
11 @brief Parses Form Descriptions, Handles User Input, and Integrates with
12 External Commands for Calculations and Data Processing.
13 */
14
15#include <common.h>
16#include <errno.h>
17#include <fcntl.h>
18#include <stdlib.h>
19#include <string.h>
20#include <sys/stat.h>
21#include <termios.h>
22#include <unistd.h>
23#include <wait.h>
24
25#define D_COMMENT '#'
26#define D_CMD '!'
27#define D_FIELD 'F'
28#define D_TEXT 'T'
29#define D_HELP '?'
30#define D_HEADER 'H'
31#define D_CALC 'C'
32#define D_QUERY 'Q'
33#define D_GETTER 'G'
34
35unsigned int display_form(Init *);
36void form_display_fields(Form *);
37int field_navigator(Form *);
38int form_parse_desc(Form *);
39int form_read_data(Form *);
40int form_write(Form *);
42int form_desc_error(int, char *, char *);
43int form_exec_cmd(Form *);
44int form_exec_receiver(Init *);
45int form_process(Init *);
46int form_post(Init *);
47int init_form(Init *, int, char **, int, int);
48int form_engine(Init *);
49int form_yx_to_fidx(Form *, int, int);
50
51/** @brief Initialize form data structure and parse description file
52 @ingroup form_engine
53 @param init A pointer to the Init structure containing form data and state.
54 @param argc The number of command line arguments passed to the form.
55 @param argv The array of command line arguments passed to the form.
56 @param begy The y-coordinate for the top-left corner of the form window.
57 @param begx The x-coordinate for the top-left corner of the form window.
58 @return 0 on success, or a non-zero value if an error occurs during
59*/
60int init_form(Init *init, int argc, char **argv, int begy, int begx) {
61 int rc;
62 char tmp_str[MAXLEN];
63 if (init->form != nullptr)
65 init->form = new_form(init, argc, argv, begy, begx);
66 form = init->form;
67 if (!form->f_mapp_spec) {
68 if (form->mapp_spec[0] == '\0') {
69 rc = Perror("Error: No form specification file given");
70 } else {
71 strnz__cpy(tmp_str, "form->mapp_spec: ", MAXLEN - 1);
73 strnz__cat(tmp_str, " not found", MAXLEN - 1);
74 rc = Perror(tmp_str);
75 }
77 return rc;
78 }
79 if (begy != 0)
80 form->begy = begy;
81 if (begx != 0)
82 form->begx = begx;
83 if ((form->f_in_spec && (form->in_spec[0] == '\0')) ||
84 (strcmp(form->in_spec, "-") == 0) ||
85 strcmp(form->in_spec, "/dev/stdin") == 0) {
86 strnz__cpy(form->in_spec, "/dev/stdin", MAXLEN - 1);
87 form->f_in_pipe = true;
88 }
89 if (form->title[0] == '\0')
91#ifdef DEBUG_IMMEDOK
92 immedok(form->win, true);
93 immedok(form->box, true);
94#endif
95 rc = form_engine(init);
97 if (form->win)
100 return rc;
101}
102/** @brief Form main processing loop
103 @ingroup form_engine
104 @param init A pointer to the Init structure containing form data and state.
105 @return 0 on successful completion, or a non-zero value if the user cancels
106 the form or if an error occurs during processing.
107 1. Parse the form description file to populate the form data structure.
108 2. Read any initial data for the form fields from a specified input
109 source.
110 3. Display the form on the screen with the initial field values.
111 4. Enter a loop to handle user input for field entry, calculation, help,
112 and cancellation:
113 a. If the user selects the accept action, perform any necessary
114 calculations or post-processing, and then either return to field entry
115 or exit the loop if the form is accepted.
116 b. If the user selects the help action, display the help screen and
117 return to the form after the user exits the help screen.
118 c. If the user selects the cancel action, exit the loop and return a
119 cancel status. */
120int form_engine(Init *init) {
121 char tmp_str[MAXLEN];
122 int form_action;
123 char *eargv[MAXARGS];
124 int eargc;
125
126 form = init->form;
127 if (form == nullptr) {
128 Perror("FORM: form data structure is nullptr");
129 }
131 return 0;
132 }
133
135 display_form(init);
137 set_chyron_key(form->chyron, 1, "F1 Help", KEY_F(1));
138 set_chyron_key(form->chyron, 9, "F9 Cancel", KEY_F(9));
139 set_chyron_key(form->chyron, 10, "F10 Continue", KEY_F(10));
142 form->fidx = 0;
143 form_action = 0;
144 while (1) {
145 if (form_action == 0 || form_action == FA_CONTINUE)
146 form_action = field_navigator(form);
147 switch (form_action) {
148 case FA_ACCEPT:
150 form_action = form_process(init);
151 else
152 form_action = form_post(init);
153 if (form_action == FA_HELP || form_action == FA_CANCEL ||
154 form_action == FA_CONTINUE || form_action == FA_END)
155 continue;
156 if (form_action == FA_ACCEPT) {
157 form_action = FA_END;
158 continue;
159 }
160 break;
161 case FA_END:
162 if (form->f_out_spec || form->out_spec[0] != '\0')
166 break;
167 case FA_HELP:
168 if (form->f_help_spec && form->help_spec[0] != '\0')
170 else {
171 strnz__cpy(tmp_str, init->mapp_help, MAXLEN - 1);
172 strnz__cat(tmp_str, "/", MAXLEN - 1);
174 }
175 eargc = 0;
176 eargv[eargc++] = strdup("view");
177 eargv[eargc++] = strdup("-Nf");
178 eargv[eargc++] = strdup(tmp_str);
179 eargv[eargc] = nullptr;
180 init->lines = 30;
181 init->cols = 66;
182 init->begy = form->begy + 1;
183 init->begx = form->begx + 1;
184 strnz__cpy(init->title, "Form Help", MAXLEN - 1);
185 popup_view(init, eargc, eargv, init->lines, init->cols, init->begy,
186 init->begx);
187 destroy_argv(eargc, eargv);
188 form_action = FA_CONTINUE;
189 break;
190 case FA_CANCEL:
191 return FA_CANCEL;
192 default:
193 form_action = FA_CONTINUE;
194 break;
195 }
196 if (form_action == FA_END)
197 break;
198 }
199 return 0;
200}
201/** @brief Handle post-processing after field entry, allowing user to edit data,
202 execute a provider command, or write data to an output file.
203 @ingroup form_engine
204 @param init A pointer to the Init structure containing form data and state.
205 @return An integer status code indicating the next action for the form
206 processing loop (e.g., FA_CONTINUE, FA_CANCEL, FA_ACCEPT, FA_HELP). */
207int form_post(Init *init) {
208 bool loop = true;
209 int c, rc;
210 click_y = click_x = -1;
211 form = init->form;
212 wmove(form->win, form->lines - 1, 0);
213 wclrtoeol(form->win);
215 set_chyron_key(form->chyron, 8, "F8 Edit", KEY_F(8));
216 set_chyron_key(form->chyron, 10, "F10 Commit", KEY_F(10));
218 rc = -1;
219 while (loop) {
220 if (rc == -1) {
223 tcflush(2, TCIFLUSH);
224 wrefresh(form->box);
226 }
227 switch (c) {
228 case KEY_F(1):
229 return FA_HELP;
230 case KEY_F(8):
232 loop = false;
233 rc = FA_CONTINUE;
234 break;
235 }
236 continue;
237 case KEY_F(9):
238 loop = false;
239 rc = FA_CANCEL;
240 break;
241 case KEY_F(10):
242 loop = false;
243 rc = FA_ACCEPT;
244 break;
245 case KEY_MOUSE:
246 continue;
247 default:
248 break;
249 }
250 }
252 return rc;
253}
254
255/** @brief Handle integration with a getter program which will provide field
256 data.
257 @ingroup form_engine
258 @param init A pointer to the Init structure containing form data and state.
259 @return An integer status code indicating the next action for the form
260 processing loop (e.g., FA_CONTINUE, FA_CANCEL, FA_ACCEPT, FA_HELP).
261 @details This function provides integration with getter programs.
262 The requirements are:
263 1. The form description file must have a line containing only 'C'
264 (calculate), 'Q' (query), org 'G' (getter) to indicate that the form supports
265 a getter.
266 2. The form description file must specify the provider command using a
267 line starting with '!' followed by the command and its arguments.
268 3. The getter program must be able to accept field data from a file,
269 from standard input, or as command line parameters.
270 4. The getter program must output field values in a format that can be
271 read by the form (e.g., one value per line), either to a file or to standard
272 output.
273
274 The sequence of operations is as follows:
275
276 1. The 'C', 'Q', or 'G' option causes Form to pause and display an
277 KEY_F(5) Calculate, Query, or Getter. option on the chyron.
278 2. The user can then cancel the operation by pressing KEY_F(9) or
279 activate the getter option by pressing KEY_F(5).
280 3. Form outputs its data to file, standard output, or as command line
281 parameters.
282 4. Form executes the getter program.
283 5. The getter program processes the data and outputs the results.
284 6. Form reads the results and populates the appropriate form fields.
285 7. Form presents the user with an option to edit the data, and the
286 sequence restarts at 1.
287
288 This function forks and executes the getter executable as a child
289 process, creates a pipe to read the output from the provider command,
290 reads the output, and updates the Form fields.
291 */
292int form_process(Init *init) {
293 int i, c, rc;
294 char tmp_str[MAXLEN];
295 char earg_str[MAXLEN + 1];
296 char *eargv[MAXARGS];
297 int eargc;
298 char file_spec[MAXLEN];
299 bool loop = true;
300 pid_t pid;
301 int pipe_fd[2];
302
303 form = init->form;
304 wmove(form->win, form->lines - 1, 0);
305 wclrtoeol(form->win);
308 strnz__cpy(tmp_str, "F5 ", MAXLEN - 1);
309
310 if (form->f_process)
311 strnz__cat(tmp_str, "Process", MAXLEN - 1);
312 else if (form->f_calculate)
313 strnz__cat(tmp_str, "Calculate", MAXLEN - 1);
314 else if (form->f_query)
315 strnz__cat(tmp_str, "Query", MAXLEN - 1);
317
318 while (loop) {
322 click_y = click_x = -1;
323 tcflush(2, TCIFLUSH);
324 wrefresh(form->box);
326 switch (c) {
327 case KEY_F(1):
328 return FA_HELP;
329 case KEY_F(5):
334 for (i = 0; i < form->fcnt; i++) {
335 strnz__cat(earg_str, " ", MAXLEN - 1);
337 }
338 eargc = str_to_args(eargv, earg_str, MAXARGS);
339 strnz__cpy(file_spec, eargv[0], MAXLEN - 1);
340 base_name(eargv[0], file_spec);
341 if (pipe(pipe_fd) == -1) {
342 destroy_argv(eargc, eargv);
343 Perror("pipe(pipe_fd) failed in init_form");
344 return (1);
345 }
346 if ((pid = fork()) == -1) {
347 destroy_argv(eargc, eargv);
348 Perror("fork() failed in init_form");
349 return (1);
350 }
351 if (pid == 0) { // Child
352 /** Prevent child process from writing to terminal */
353 int dev_null = open("/dev/null", O_WRONLY);
354 if (dev_null == -1) {
355 Perror("open(/dev/null) failed in init_pick child "
356 "process");
357 exit(EXIT_FAILURE);
358 }
359 dup2(dev_null, STDERR_FILENO);
360 close(dev_null);
361 close(pipe_fd[P_READ]);
362 dup2(pipe_fd[P_WRITE], STDOUT_FILENO);
363 close(pipe_fd[P_WRITE]);
364 execvp(eargv[0], eargv);
365 ssnprintf(em0, MAXLEN, "%s, line: %d", __FILE__,
366 __LINE__ - 2);
367 strnz__cpy(em1, "execvp(", MAXLEN - 1);
368 strnz__cat(em1, eargv[0], MAXLEN - 1);
369 strnz__cat(em1, ", ", MAXLEN - 1);
370 strnz__cat(em1, earg_str, MAXLEN - 1);
372 strerror_r(errno, em2, MAXLEN);
374 exit(EXIT_FAILURE);
375 } // Back to parent
376 close(pipe_fd[P_WRITE]);
377 form->in_fp = fdopen(pipe_fd[P_READ], "rb");
378 form->f_in_pipe = true;
380 close(pipe_fd[P_READ]);
382 destroy_argv(eargc, eargv);
384 set_chyron_key_cp(form->chyron, 5, "F5 Edit", KEY_F(5),
386 set_chyron_key(form->chyron, 10, "F10 Commit", KEY_F(10));
388 continue;
389 }
390 break;
391 case KEY_F(8):
393 loop = false;
394 rc = FA_CONTINUE;
395 break;
396 }
397 continue;
398 case KEY_F(9):
399 loop = false;
400 rc = FA_CANCEL;
401 break;
402 case KEY_F(10):
403 loop = false;
404 rc = FA_ACCEPT;
405 break;
406 case KEY_MOUSE:
407 break;
408 default:
409 break;
410 }
411 }
414 return rc;
415}
416/** @brief Handle user input for field entry, allowing navigation between fields
417 and looping until an exit action is selected.
418 @ingroup form_engine
419 @param form A pointer to the Form structure containing form data and state.
420 @return An integer status code indicating the next action for the form
421 processing loop (e.g., FA_ACCEPT, FA_HELP, FA_CALC, FA_CANCEL).
422 @details This function manages user input for field entry, including
423 navigation between fields and handling of special keys for accepting,
424 canceling, requesting help, or performing calculations. The function loops
425 until the user selects an exit action (e.g., accept or cancel). */
426int field_navigator(Form *form) {
427
428 if (form->fidx < 0)
429 return (-1);
430 while (1) {
432
433 switch (cmd_key) {
434 case KEY_F(10):
435 return (FA_ACCEPT);
436 case KEY_F(1):
437 return (FA_HELP);
438 case KEY_F(5):
439 if (form->f_process)
440 return (FA_CALC);
441 break;
442 case KEY_F(9):
443 return (FA_CANCEL);
444 case 'k':
445 case KEY_UP:
446 if (form->fidx != 0)
447 form->fidx--;
448 break;
449 case '\r':
450 case KEY_ENTER:
451 if (form->fidx < form->fcnt - 1)
452 form->fidx++;
453 else if (form->fidx == form->fcnt - 1)
454 return (FA_ACCEPT);
455 break;
456 case 'j':
457 case KEY_DOWN:
458 if (form->fidx < form->fcnt - 1)
459 form->fidx++;
460 else if (form->fidx == form->fcnt - 1)
461 return (FA_ACCEPT);
462 break;
463 case KEY_MOUSE:
464 break;
465 default:
466 break;
467 }
468 }
469}
470int form_yx_to_fidx(Form *form, int y, int x) {
471 for (int i = 0; i < form->fcnt; i++) {
472 if (y == form->field[i]->line && x >= form->field[i]->col &&
473 x < form->field[i]->col + form->field[i]->len) {
474 return i;
475 }
476 }
477 return -1; // No field found at the given coordinates
478}
479
480/** @brief Display the form on the screen, including text elements and fields,
481 and set up the form window based on the form configuration.
482 @ingroup form_engine
483 @param init A pointer to the Init structure containing form data and state.
484 @return 0 on success, or a non-zero value if an error occurs while
485 creating the form window or rendering the form elements. */
486unsigned int display_form(Init *init) {
487 int n, flin, fcol;
488 char tmp_str[MAXLEN];
489
490 form = init->form;
491 form->lines = 0;
492 for (n = 0; n < form->dcnt; n++)
495 for (n = 0; n < form->fcnt; n++)
498 form->lines += 3;
499 if (form->lines > (LINES - form->begy))
500 form->lines = LINES - form->begy;
501 for (n = 0; n < form->fcnt; n++) {
502 if (form->field[n]->line >= (form->lines - 2))
503 form->fcnt = n;
504 }
505 form->cols += 2;
506 if (form->cols > (COLS - form->begx - 3))
507 form->cols = COLS - form->begx - 3;
509 true)) {
510 strnz__cpy(tmp_str, "box_new failed: ", MAXLEN - 1);
512 Perror(tmp_str);
513 return (1);
514 }
515#ifdef DEBUG_IMMEDOK
516 immedok(form->win, TRUE);
517#endif
520 wnoutrefresh(form->win);
522 if (form->brackets[0] != '\0' && form->brackets[1] != '\0') {
523 flin = form->field[form->fidx]->line + 1;
525 // mvwadd_wch(form->box, flin, fcol, &form->brktl);
526 mvwaddch(form->box, flin, fcol, form->brackets[0]);
527 fcol += form->field[form->fidx]->len + 1;
528 // mvwadd_wch(box, flin, fcol, &form->brktr);
529 mvwaddch(form->box, flin, fcol, form->brackets[1]);
530 }
531 }
532 wnoutrefresh(form->box);
533 for (n = 0; n < form->dcnt; n++) {
536 form->text[n]->str);
537 wnoutrefresh(form->win);
538 }
540 return 0;
541}
542/** @brief Display form fields on the screen, populating field values and
543 formatting them according to the form configuration.
544 @ingroup form_engine
545 @param form A pointer to the Form structure containing form data and
546 state.
547 @details This function iterates through the defined form fields, formats
548 their display values based on the specified fill character and field
549 length, and renders them on the form window. It also updates the chyron
550 with available commands for user interaction. */
551void form_display_fields(Form *form) {
552 int flin, fcol;
553 char fill_char = form->fill_char[0];
554 for (form->fidx = 0; form->fidx < form->fcnt; form->fidx++) {
555 if (form->field[form->fidx]->col + form->field[form->fidx]->len + 2 >
556 form->cols)
557 form->field[form->fidx]->len =
558 form->cols - (form->field[form->fidx]->col + 2);
559 strnfill(form->field[form->fidx]->filler_s, fill_char,
560 form->field[form->fidx]->len);
562 flin = form->field[form->fidx]->line;
563 fcol = form->field[form->fidx]->col;
564 mvwaddstr(form->win, flin, fcol, form->field[form->fidx]->filler_s);
565 mvwaddstr(form->win, flin, fcol, form->field[form->fidx]->display_s);
566 wnoutrefresh(form->win);
567 }
568 return;
569}
570/** @brief Parse the form description file to populate the Form data
571 structure with field definitions, text elements, and other configuration
572 specified in the description file.
573 @ingroup form_engine
574 @param form A pointer to the Form structure containing form data and
575 state.
576 @return 0 on success, or a non-zero value if an error occurs while
577 parsing the description file (e.g., file not found, invalid format,
578 missing directives). */
579int form_parse_desc(Form *form) {
580 FILE *form_desc_fp;
581 char *token;
582 char *s;
583 int cols = 0;
584 int i, l;
585 int in_line_num = 0;
586 char in_buf[BUFSIZ];
587 char tmp_buf[BUFSIZ];
588 char *tmp_buf_p;
589 char tmp_str[BUFSIZ];
590 char delim[5];
591 char directive;
592
593 form_desc_fp = fopen(form->mapp_spec, "r");
594 if (form_desc_fp == nullptr) {
595 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 2);
596 strnz__cpy(em1, "fopen ", MAXLEN - 1);
598 strerror_r(errno, em2, MAXLEN);
600 return (1);
601 }
602 for (i = 0; i < FIELD_MAXCNT; i++) {
603 form->field[i] = calloc(1, sizeof(Field));
604 if (!form->field[i]) {
605 sprintf(tmp_str, "FORM: calloc failed for fields");
606 abend(EXIT_FAILURE, tmp_str);
607 }
608 }
609 for (i = 0; i < FIELD_MAXCNT; i++) {
610 form->text[i] = calloc(1, sizeof(Text));
611 if (!form->text[i]) {
612 sprintf(tmp_str, "FORM: calloc failed for text");
613 abend(EXIT_FAILURE, tmp_str);
614 }
615 }
616 form->didx = 0;
617 form->fidx = 0;
618 form->fcnt = 0;
619 form->cols = 34;
620 while ((fgets(in_buf, MAXLEN, form_desc_fp)) != nullptr) {
621 s = in_buf;
622 in_line_num++;
623 l = trim(in_buf);
624 if (l == 0)
625 continue;
626 if (*s == D_COMMENT)
627 continue;
628 delim[0] = '\n';
629 delim[1] = in_buf[1];
630 delim[2] = '\0';
631 strnz__cpy(tmp_buf, in_buf, MAXLEN - 1);
632 tmp_buf_p = tmp_buf;
633 if (!(token = strtok(tmp_buf_p, delim))) {
634 continue;
635 }
636 directive = *token;
637 switch ((int)directive) {
638 case D_COMMENT:
639 break;
640 case D_CALC:
641 form->f_calculate = true;
642 break;
643 case D_QUERY:
644 form->f_query = true;
645 break;
646 case D_GETTER:
647 form->f_process = true;
648 break;
649 case D_CMD:
650 if (!(token = strtok(nullptr, delim))) {
651 form_desc_error(in_line_num, in_buf,
652 "FORM: receiver_cmd delimiter");
653 continue;
654 }
656 break;
657 case D_HELP:
658 if (!(token = strtok(nullptr, delim))) {
659 form_desc_error(in_line_num, in_buf,
660 "FORM: help_spec delimiter");
661 }
662 strnz__cpy(form->help_spec, token, MAXLEN - 1);
663 break;
664 case D_FIELD:
665 if (form->field[form->fidx] == nullptr) {
666 sprintf(tmp_str, "FORM: calloc failed for fields");
667 abend(EXIT_FAILURE, tmp_str);
668 }
669 if (!(token = strtok(nullptr, delim))) {
670 form_desc_error(in_line_num, in_buf,
671 "FORM: line number delimiter");
672 return 1;
673 }
674 form->field[form->fidx]->line = atoi(token);
675 if (form->field[form->fidx]->line < 0 ||
676 form->field[form->fidx]->line >= FIELD_MAXCNT) {
677 form_desc_error(in_line_num, in_buf,
678 "FORM: invalid line number");
679 return 1;
680 }
681 if (!(token = strtok(nullptr, delim))) {
682 form_desc_error(in_line_num, in_buf,
683 "FORM: column number delimiter");
684 return 1;
685 }
686 form->field[form->fidx]->col = atoi(token);
687 if (form->field[form->fidx]->col < 0 ||
688 form->field[form->fidx]->col >= FIELD_MAXLEN) {
689 form_desc_error(in_line_num, in_buf,
690 "FORM: invalid column number");
691 break;
692 }
693 if (!(token = strtok(nullptr, delim))) {
694 strnz__cpy(tmp_str, in_buf, MAXLEN - 1);
695 form_desc_error(in_line_num, tmp_str, "FORM: length delimiter");
696 break;
697 }
698 form->field[form->fidx]->len = atoi(token);
699 if (form->field[form->fidx]->len < 0 ||
700 form->field[form->fidx]->len > FIELD_MAXLEN) {
701 form_desc_error(in_line_num, in_buf, "FORM: invalid length");
702 break;
703 }
704 if (!(token = strtok(nullptr, delim))) {
705 form_desc_error(in_line_num, in_buf,
706 "FORM: validation code delimiter");
707 break;
708 }
709 form->field[form->fidx]->ff = -1;
710 for (i = 0; i < FF_INVALID; i++) {
711 str_to_lower(token);
713 if (!strcmp(token, ff_tbl[i])) {
714 form->field[form->fidx]->ff = i;
715 break;
716 }
717 }
718 if (form->field[form->fidx]->ff < 0 ||
719 form->field[form->fidx]->ff >= FF_INVALID) {
720 form_desc_error(in_line_num, in_buf,
721 "FORM: invalid format code");
722 break;
723 }
724 cols =
725 form->field[form->fidx]->col + form->field[form->fidx]->len + 1;
726 if (cols > form->cols)
727 form->cols = cols;
728 form->fidx++;
729 form->fcnt = form->fidx;
730 break;
731 case D_TEXT:
732 if (form->text[form->didx] == nullptr) {
733 sprintf(tmp_str, "FORM: calloc failed for text");
734 abend(EXIT_FAILURE, tmp_str);
735 }
736 if (!(token = strtok(nullptr, delim))) {
737 form_desc_error(in_line_num, in_buf,
738 "FORM: line number delimiter");
739 break;
740 }
741 form->text[form->didx]->line = atoi(token);
742 if (form->text[form->didx]->line < 0 ||
743 form->text[form->didx]->line >= FIELD_MAXCNT) {
744 form_desc_error(in_line_num, in_buf,
745 "FORM: invalid line number");
746 break;
747 }
748 if (!(token = strtok(nullptr, delim))) {
749 form_desc_error(in_line_num, in_buf,
750 "FORM: column number delimiter");
751 break;
752 }
753 form->text[form->didx]->col = atoi(token);
754 if (form->text[form->didx]->col < 0 ||
755 form->text[form->didx]->col >= FIELD_MAXLEN) {
756 form_desc_error(in_line_num, in_buf,
757 "FORM: invalid column number");
758 break;
759 }
760 if (!(token = strtok(nullptr, delim))) {
761 form_desc_error(in_line_num, in_buf, "FORM: text delimiter");
762 break;
763 }
764 strnz__cpy(form->text[form->didx]->str, token, MAXLEN - 1);
765 form->text[form->didx]->len = strlen(form->text[form->didx]->str);
766 if (form->text[form->didx]->len < 0 ||
767 form->text[form->didx]->len > FIELD_MAXLEN) {
768 form_desc_error(in_line_num, in_buf, "FORM: invalid length");
769 break;
770 }
771 cols =
772 form->text[form->didx]->col + form->text[form->didx]->len + 1;
773 if (cols > form->cols)
774 form->cols = cols;
775 form->didx++;
776 form->dcnt = form->didx;
777 break;
778 case D_HEADER:
779 if ((token = strtok(nullptr, delim))) {
780 strnz__cpy(form->title, token, MAXLEN - 1);
781 }
782 break;
783 default:
784 form_desc_error(in_line_num, in_buf, "invalid directive");
785 break;
786 }
787 }
788 fclose(form_desc_fp);
789 if (form->didx < 1 && form->fidx < 1) {
790 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__);
791 ssnprintf(em1, MAXLEN - 1, "%s", "Error in description file:");
794 return (1);
795 }
796 return (0);
797}
798/** @brief Read initial data for form fields from a specified input source,
799 such as a file or standard input, and populate the form fields with the
800 data.
801 @ingroup form_engine
802 @param form A pointer to the Form structure containing form data and
803 state.
804 @return 0 on success, or a non-zero value if an error occurs while
805 reading the data or if the specified input source is invalid or empty. */
806int form_read_data(Form *form) {
807 struct stat sb;
808 char in_buf[MAXLEN];
809 char field[MAXLEN];
810
811 if (!form->f_in_pipe) {
812 if (form->f_in_spec && form->in_spec[0] != '\0') {
813 if ((lstat(form->in_spec, &sb) == -1) || (sb.st_size == 0) ||
814 ((form->in_fp = fopen(form->in_spec, "rb")) == nullptr)) {
816 if (sb.st_size == 0)
817 strnz__cpy(em1, "File is empty", MAXLEN - 1);
818 else
819 strnz__cpy(em1, "File does not exist", MAXLEN - 1);
820 strnz__cpy(em2, "Fields will be blank or zero", MAXLEN - 1);
822 if (cmd_key == KEY_F(9))
823 return (1);
824 }
825 if (form->in_fp == nullptr)
826 return (1);
827 } else
828 return (0);
829 }
830 form->fidx = 0;
831 if (form->in_fp != nullptr) {
832 while ((fgets(in_buf, MAXLEN, form->in_fp)) != nullptr) {
833 if (form->fidx < FIELD_MAXCNT)
834 strnz__cpy(field, in_buf, MAXLEN - 1);
835 form_fmt_field(form, field);
836 form->fidx++;
837 }
838 fclose(form->in_fp);
839 }
840 return (0);
841}
842/** @brief Execute a command specified in the form description file, passing
843 form field values as arguments, and optionally redirecting output to a file.
844 @ingroup form_engine
845 @param form A pointer to the Form structure containing form data and state.
846 @return 0 on success, or a non-zero value if an error occurs while
847 constructing or executing the command. */
848int form_exec_cmd(Form *form) {
849 char earg_str[MAXLEN + 1];
850 int i;
851 strnz__cpy(earg_str, form->receiver_cmd, MAXLEN - 1);
852 for (i = 0; i < form->fcnt; i++) {
853 strnz__cat(earg_str, " ", MAXLEN - 1);
854 strnz__cat(earg_str, form->field[i]->accept_s, MAXLEN - 1);
855 }
856 if (form->f_out_spec && form->f_process) {
857 strnz__cat(earg_str, " >", MAXLEN - 1);
858 strnz__cat(earg_str, form->out_spec, MAXLEN - 1);
859 }
860 shell(earg_str);
861 return 0;
862}
863/** @brief Execute a command specified by the -R option on the form command line
864 @ingroup form_engine
865 @param init A pointer to the Init structure
866 @return 0 on success, or a non-zero value if an error occurs while
867 constructing or executing the command.
868 @details This function constructs a command by taking the receiver_cmd
869 appending field values as arguments, and handling special cases such as
870 multiple command arguments or placeholder substitution. It then executes the
871 command, either displaying the output in a popup view if the command is
872 "view", or executing it directly in a child process for other commands. The
873 function also handles error cases and ensures that resources are properly
874 freed. */
875int form_exec_receiver(Init *init) {
876 int rc = -1;
877 int eargc;
878 char *eargv[MAXARGS];
879 char tmp_str[MAXLEN] = {'\0'};
880 char title[MAXLEN];
881 char sav_arg[MAXLEN];
882 char *out_s;
883 int eargx = 0;
884 int i = 0;
885 pid_t pid = 0;
886 bool f_append_values = false;
887
888 title[0] = '\0';
889 if (form->receiver_cmd[0] == '\0')
890 return -1;
891 // Eliminate leading and trailing quotes
892 if (form->receiver_cmd[0] == '\\' || form->receiver_cmd[0] == '\"') {
893 size_t len = strlen(form->receiver_cmd);
894 if (len > 1 && form->receiver_cmd[len - 1] == '\"') {
895 memmove(form->receiver_cmd, form->receiver_cmd + 1, len - 2);
896 form->receiver_cmd[len - 2] = '\0';
897 }
898 }
900 tmp_str[0] = '\0';
902 // concatenate fields into a single argument
903 for (i = 0; i < form->fcnt; i++) {
904 if (form->field[i]->accept_s[0] && eargc < MAXARGS) {
905 if (tmp_str[0] != '\0')
906 strnz__cat(tmp_str, " ", MAXLEN - 1);
908 }
909 }
910 eargv[eargc++] = strdup(tmp_str);
911 } else {
912 f_append_values = false;
913 // find lines with "%%" to determine where to insert field values
914 i = 0;
915 while (i < eargc) {
916 /** This is the line that gets the selected objects */
917 if (strstr(eargv[i], "%%") != nullptr) {
918 tmp_str[0] = '\0';
919 f_append_values = true;
920 strnz__cpy(sav_arg, eargv[i], MAXLEN - 1);
921 eargx = i;
922 break;
923 }
924 i++;
925 }
926 for (i = 0; i < form->fcnt; i++) {
927 // concatenate field values onto tmp_str
928 if (form->field[i]->accept_s[0] && eargc < MAXARGS - 1) {
929 if (f_append_values == true) {
930 if (tmp_str[0] != '\0')
931 strnz__cat(tmp_str, " ", MAXLEN - 1);
933 continue;
934 }
935 eargv[eargc++] = strdup(form->field[i]->accept_s);
936 }
937 }
938 // replace "%%" with concatenated field values
939 if (f_append_values == true) {
940 if (eargv[eargx] != nullptr) {
941 free(eargv[eargx]);
942 eargv[eargx] = nullptr;
943 }
944 out_s = rep_substring(sav_arg, "%%", tmp_str);
945 if (out_s == nullptr || out_s[0] == '\0') {
946 i = 0;
947 while (i < eargc) {
948 if (eargv[i] != nullptr)
949 free(eargv[i]);
950 i++;
951 }
952 Perror("rep_substring() failed in form_exec_objects");
953 return 1;
954 }
955 strnz__cpy(title, out_s, MAXLEN - 1);
956 eargv[eargx] = strdup(out_s);
957 if (out_s != nullptr) {
958 free(out_s);
959 out_s = nullptr;
960 }
961 }
962 }
963 if (eargc > 0)
964 eargv[eargc] = nullptr;
965 base_name(tmp_str, eargv[0]);
966 if (tmp_str[0] != '\0' && (strcmp(tmp_str, "view") == 0)) {
967 /** initialize popup_view arguments and execute popup_view to display
968 command output within form interface */
969 init->lines = 60;
970 init->cols = 80;
971 init->begy = form->begy + 1;
972 init->begx = form->begx + 1;
973 if (title[0] != '\0')
974 strnz__cpy(init->title, title, MAXLEN - 1);
975 else
976 strnz__cpy(init->title, eargv[eargc], MAXLEN - 1);
977 popup_view(init, eargc, eargv, init->lines, init->cols, init->begy,
978 init->begx);
979 i = 0;
980 while (i < eargc) {
981 if (eargv[i] != nullptr)
982 free(eargv[i]);
983 i++;
984 }
985 return 0;
986 } else {
987 if ((pid = fork()) == -1) {
988 /** fork failed, free eargv and return error */
989 i = 0;
990 while (i < eargc) {
991 if (eargv[i] != nullptr)
992 free(eargv[i]);
993 i++;
994 }
995 Perror("fork() failed in form_exec_objects");
996 return (1);
997 }
998 if (pid == 0) {
999 /** Prevent child process from writing to terminal */
1000 int dev_null = open("/dev/null", O_WRONLY);
1001 if (dev_null == -1) {
1002 Perror("open(/dev/null) failed in init_form child process");
1003 exit(EXIT_FAILURE);
1004 }
1005 dup2(dev_null, STDERR_FILENO);
1006 close(dev_null);
1007 /** Child process to execute command */
1008 execvp(eargv[0], eargv);
1009 /** If execvp returns, it means execution failed, so free eargv
1010 and print error message before exiting */
1011 strnz__cpy(tmp_str, "Can't exec form cmd: ", MAXLEN - 1);
1012 strnz__cat(tmp_str, eargv[0], MAXLEN - 1);
1013 Perror(tmp_str);
1014 exit(EXIT_FAILURE);
1015 }
1016 }
1017 waitpid(pid, nullptr, 0);
1018 destroy_argv(eargc, eargv);
1021 werase(stdscr);
1022 wrefresh(stdscr);
1024 return rc;
1025}
1026/** @brief Write form field values to a specified output destination, such
1027 as a file or standard output, based on the form configuration and user
1028 input.
1029 @ingroup form_engine
1030 @param form A pointer to the Form structure containing form data and
1031 state.
1032 @return 0 on success, or a non-zero value if an error occurs while
1033 writing the data or if the specified output destination is invalid. */
1034int form_write(Form *form) {
1035 int n;
1036 if (form->out_spec[0] == '\0' || strcmp(form->out_spec, "-") == 0 ||
1037 strcmp(form->out_spec, "/dev/stdout") == 0) {
1038 strnz__cpy(form->out_spec, "/dev/stdout", MAXLEN - 1);
1039 close(form->out_fd);
1040 form->out_fd = open(form->out_spec, O_CREAT | O_RDWR | O_TRUNC, 0644);
1041 if (form->out_fd == -1) {
1042 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 1);
1043 strnz__cpy(em1, "open ", MAXLEN - 1);
1045 strerror_r(errno, em2, MAXLEN);
1047 return (1);
1048 }
1049 dup2(form->out_fd, STDOUT_FILENO);
1050 form->out_fp = fdopen(STDOUT_FILENO, "w");
1051 form->f_out_spec = true;
1052 form->f_out_pipe = true;
1053 } else {
1054 if ((form->out_fp = fopen(form->out_spec, "w")) == nullptr) {
1055 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 1);
1056 strerror_r(errno, em2, MAXLEN);
1058 return (1);
1059 }
1060 }
1061 if (form->out_fp == nullptr) {
1062 ssnprintf(em0, MAXLEN - 1, "%s, line: %d", __FILE__, __LINE__ - 1);
1063 strnz__cpy(em1, "fopen ", MAXLEN - 1);
1065 strerror_r(errno, em2, MAXLEN);
1067 return (1);
1068 }
1069 for (n = 0; n < form->fcnt; n++)
1070 fprintf(form->out_fp, "%s\n", form->field[n]->accept_s);
1071 if (form->out_fp != nullptr)
1072 fclose(form->out_fp);
1073 return (0);
1074}
1075/** @brief Handle errors encountered while parsing the form description
1076 file, providing detailed error messages that include the file name, line
1077 number, and the specific error encountered.
1078 @ingroup form_engine
1079 @param in_line_num The line number in the description file where the
1080 error occurred.
1081 @param in_buf The content of the line that caused the error, for
1082 context.
1083 @param em A specific error message describing the nature of the error.
1084 @return An integer status code indicating how the user responded to the
1085 error message (e.g., which key they pressed to acknowledge the error). */
1086int form_desc_error(int in_line_num, char *in_buf, char *em) {
1087 int cmd_key;
1088
1089 ssnprintf(em0, MAXLEN - 1, "%s: %s", __FILE__, em);
1090 ssnprintf(em1, MAXLEN - 1, "Desc file: %s, line: %d", form->mapp_spec,
1091 in_line_num);
1092 strnz__cpy(em2, in_buf, MAXLEN - 1);
1094 return cmd_key;
1095}
int form_yx_to_fidx(Form *, int, int)
#define FIELD_MAXLEN
Definition form.h:18
@ FF_INVALID
Definition form.h:69
@ FA_END
Definition form.h:43
@ FA_ACCEPT
Definition form.h:30
@ FA_CALC
Definition form.h:38
@ FA_HELP
Definition form.h:32
@ FA_CANCEL
Definition form.h:34
@ FA_CONTINUE
Definition form.h:28
Form * form
Definition mem.c:47
#define FIELD_MAXCNT
Definition form.h:19
#define FORM_HELP_FILE
Definition common.h:35
#define P_READ
Definition common.h:47
int popup_view(Init *, int, char **, int, int, int, int)
instantiate a view popup window
Definition popups.c:146
#define P_WRITE
Definition common.h:48
#define MAXARGS
Definition cm.h:30
int wait_timeout
Definition futil.c:144
#define nullptr
Definition cm.h:25
#define BUFSIZ
Definition view.h:29
#define MAXLEN
Definition curskeys.c:15
#define D_CMD
Definition form_engine.c:26
#define D_FIELD
Definition form_engine.c:27
#define D_CALC
Definition form_engine.c:31
void form_usage()
#define D_TEXT
Definition form_engine.c:28
#define D_HELP
Definition form_engine.c:29
#define D_QUERY
Definition form_engine.c:32
#define D_COMMENT
Definition form_engine.c:25
#define D_HEADER
Definition form_engine.c:30
#define D_GETTER
Definition form_engine.c:33
unsigned int cmd_key
Definition dwin.c:126
WINDOW * win_win[MAXWIN]
Definition dwin.c:122
char em1[MAXLEN]
Definition dwin.c:142
int click_x
Definition dwin.c:49
int cp_nt_hl_rev
Definition dwin.c:151
int win_ptr
Definition dwin.c:130
int click_y
Definition dwin.c:48
char em0[MAXLEN]
Definition dwin.c:141
WINDOW * win_box[MAXWIN]
Definition dwin.c:124
char em2[MAXLEN]
Definition dwin.c:143
char ff_tbl[][26]
Definition fields.c:26
int xwgetch(WINDOW *, Chyron *, int)
Wrapper for wgetch that handles signals, mouse events, checks for clicks on the chyron line,...
Definition dwin.c:1651
int box_new(int, int, int, int, char *, bool)
Create a new window with optional box and title.
Definition dwin.c:745
void restore_wins()
Restore all windows after a screen resize.
Definition dwin.c:933
WINDOW * win_del()
Delete the current window and its associated box window.
Definition dwin.c:893
bool waitpid_with_timeout(pid_t pid, int timeout)
Wait for a process to finish with a timeout and optional user cancellation.
Definition dwin.c:1603
int Perror(char *)
Display a simple error message window or print to stderr.
Definition dwin.c:1162
int display_error(char *em0, char *em1, char *em2, char *em3)
Display an error message window or print to stderr.
Definition dwin.c:1102
void abend(int, char *)
Abnormal program termination.
Definition dwin.c:1588
bool is_set_chyron_key(Chyron *, int)
Check if function key label is set.
Definition dwin.c:1372
void set_chyron_key(Chyron *, int, char *, int)
Set chyron key with default color pair (cp_nt_rev).
Definition dwin.c:1411
void display_chyron(WINDOW *win, Chyron *chyron, int line, int col)
Display chyron on window.
Definition dwin.c:1476
Chyron * destroy_chyron(Chyron *chyron)
Destroy Chyron structure.
Definition dwin.c:1352
void set_chyron_key_cp(Chyron *, int, char *, int, int)
Set chyron key with color pair (cp).
Definition dwin.c:1388
void compile_chyron(Chyron *)
construct the chyron string from the chyron structure
Definition dwin.c:1436
void unset_chyron_key(Chyron *, int)
Unset chyron key.
Definition dwin.c:1425
Chyron * new_chyron()
Create and initialize Chyron structure.
Definition dwin.c:1336
int shell(char *)
Execute a shell command.
Definition exec.c:82
int form_fmt_field(Form *, char *)
Format field according to its format type.
Definition fields.c:417
int field_editor(Form *)
Accept input for a field.
Definition fields.c:55
int form_desc_error(int, char *, char *)
Handle errors encountered while parsing the form description file, providing detailed error messages ...
int form_exec_receiver(Init *)
Execute a command specified by the -R option on the form command line.
int form_read_data(Form *)
Read initial data for form fields from a specified input source, such as a file or standard input,...
int form_parse_desc(Form *)
Parse the form description file to populate the Form data structure with field definitions,...
int form_write(Form *)
Write form field values to a specified output destination, such as a file or standard output,...
int form_process(Init *)
Handle integration with a getter program which will provide field data.
int form_exec_cmd(Form *)
Execute a command specified in the form description file, passing form field values as arguments,...
int init_form(Init *, int, char **, int, int)
Initialize form data structure and parse description file.
Definition form_engine.c:60
int form_post(Init *)
Handle post-processing after field entry, allowing user to edit data, execute a provider command,...
int form_engine(Init *)
Form main processing loop.
void form_display_fields(Form *)
Display form fields on the screen, populating field values and formatting them according to the form ...
int field_navigator(Form *)
Handle user input for field entry, allowing navigation between fields and looping until an exit actio...
unsigned int display_form(Init *)
Display the form on the screen, including text elements and fields, and set up the form window based ...
size_t strnz__cpy(char *, const char *, size_t)
safer alternative to strncpy
Definition futil.c:435
int destroy_argv(int argc, char **argv)
Deallocates memory allocated for argument strings in argv.
Definition futil.c:385
size_t trim(char *)
Trims leading and trailing spaces from string s in place.
Definition futil.c:282
bool str_to_lower(char *)
Converts a string to lowercase.
Definition futil.c:399
bool strnfill(char *, char, int)
Fills string s with character c n.
Definition futil.c:606
size_t strnz(char *, size_t)
terminates string at New Line, Carriage Return, or max_len
Definition futil.c:506
size_t ssnprintf(char *, size_t, const char *,...)
ssnprintf was designed to be a safer alternative to snprintf.
Definition futil.c:311
size_t strnz__cat(char *, const char *, size_t)
safer alternative to strncat
Definition futil.c:464
char * rep_substring(const char *, const char *, const char *)
Replace all occurrences of "tgt_s" in "org_s" with "rep_s".
Definition futil.c:1301
bool base_name(char *, char *)
Returns the base name of a file specification.
Definition futil.c:984
int str_to_args(char **, char *, int)
Converts a string into an array of argument strings.
Definition futil.c:331
Form * new_form(Init *, int, char **, int, int)
Create and initialize Form structure.
Definition mem.c:266
Form * destroy_form(Init *init)
Destroy Form structure.
Definition mem.c:289
bool restore_curses_tioctl()
restore_curses_tioctl() - restore curses terminal settings
Definition scriou.c:81
void sig_prog_mode()
Set up signal handlers for interrupt signals.
Definition sig.c:62
char title[MAXLEN]
Definition common.h:120
char mapp_help[MAXLEN]
Definition common.h:137
int begx
Definition common.h:109
Form * form
Definition common.h:171
int cols
Definition common.h:107
int begy
Definition common.h:108
int lines
Definition common.h:106
int l
Definition cm.h:280
int len
Definition form.h:108
int col
Definition form.h:104
char str[SCR_COLS]
Definition form.h:106
int line
Definition form.h:102
int line
Definition form.h:115
char display_s[FIELD_MAXLEN]
Definition form.h:132
char accept_s[FIELD_MAXLEN]
Definition form.h:128
int ff
Definition form.h:121
int col
Definition form.h:117
int len
Definition form.h:119
char filler_s[FIELD_MAXLEN]
Definition form.h:137
FILE * out_fp
Definition form.h:161
Text * text[FIELD_MAXCNT]
Definition form.h:338
bool f_in_pipe
Definition form.h:221
bool f_mapp_spec
Definition form.h:192
WINDOW * box
Definition form.h:156
Field * field[FIELD_MAXCNT]
Definition form.h:351
char receiver_cmd[MAXLEN]
Definition form.h:178
bool f_process
Definition form.h:246
int didx
Definition form.h:323
int begy
Definition form.h:151
char provider_cmd[MAXLEN]
Definition form.h:170
bool f_query
Definition form.h:248
int out_fd
Definition form.h:165
int cols
Definition form.h:150
int lines
Definition form.h:149
int fcnt
Definition form.h:316
int begx
Definition form.h:153
bool f_out_pipe
Definition form.h:226
int fidx
Definition form.h:308
bool f_provider_cmd
Definition form.h:256
bool f_calculate
Definition form.h:244
char fill_char[2]
Definition form.h:297
bool f_help_spec
Definition form.h:231
FILE * in_fp
Definition form.h:159
char out_spec[MAXLEN]
Definition form.h:208
Chyron * chyron
Definition form.h:363
bool f_receiver_cmd
Definition form.h:263
bool f_multiple_cmd_args
Definition form.h:243
char in_spec[MAXLEN]
Definition form.h:201
char mapp_spec[FIELD_MAXLEN]
Definition form.h:167
char title[MAXLEN]
Definition form.h:157
char brackets[3]
Definition form.h:279
int dcnt
Definition form.h:330
bool f_out_spec
Definition form.h:218
WINDOW * win
Definition form.h:155
bool f_in_spec
Definition form.h:215
char help_spec[MAXLEN]
Definition form.h:194