ncurses 6.1 - patch 20190817
[ncurses.git] / test / cardfile.c
1 /****************************************************************************
2  * Copyright (c) 1999-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 /*
30  * Author: Thomas E. Dickey
31  *
32  * $Id: cardfile.c,v 1.46 2019/08/17 21:49:40 tom Exp $
33  *
34  * File format: text beginning in column 1 is a title; other text is content.
35  */
36
37 #include <test.priv.h>
38
39 #if USE_LIBFORM && USE_LIBPANEL
40
41 #include <form.h>
42 #include <panel.h>
43
44 #define VISIBLE_CARDS 10
45 #define OFFSET_CARD 2
46 #define pair_1 1
47 #define pair_2 2
48
49 #define isVisible(cardp) ((cardp)->panel != 0)
50
51 enum {
52     MY_CTRL_x = MAX_FORM_COMMAND
53     ,MY_CTRL_N
54     ,MY_CTRL_P
55     ,MY_CTRL_Q
56     ,MY_CTRL_W
57 };
58
59 typedef struct _card {
60     struct _card *link;
61     PANEL *panel;
62     FORM *form;
63     char *title;
64     char *content;
65 } CARD;
66
67 static CARD *all_cards;
68 static bool try_color = FALSE;
69 static char default_name[] = "cardfile.dat";
70
71 static void
72 failed(const char *s)
73 {
74     perror(s);
75     endwin();
76     ExitProgram(EXIT_FAILURE);
77 }
78
79 static const char *
80 skip(const char *buffer)
81 {
82     while (isspace(UChar(*buffer)))
83         buffer++;
84     return buffer;
85 }
86
87 static void
88 trim(char *buffer)
89 {
90     size_t n = strlen(buffer);
91     while (n-- && isspace(UChar(buffer[n])))
92         buffer[n] = 0;
93 }
94
95 /*******************************************************************************/
96
97 static CARD *
98 add_title(const char *title)
99 {
100     CARD *card, *p, *q;
101
102     for (p = all_cards, q = 0; p != 0; q = p, p = p->link) {
103         int cmp = strcmp(p->title, title);
104         if (cmp == 0)
105             return p;
106         if (cmp > 0)
107             break;
108     }
109
110     card = typeCalloc(CARD, (size_t) 1);
111     card->title = strdup(title);
112     card->content = strdup("");
113
114     if (q == 0) {
115         card->link = all_cards;
116         all_cards = card;
117     } else {
118         card->link = q->link;
119         q->link = card;
120     }
121
122     return card;
123 }
124
125 static void
126 add_content(CARD * card, const char *content)
127 {
128     size_t total;
129
130     content = skip(content);
131     if ((total = strlen(content)) != 0) {
132         size_t offset;
133
134         if (card->content != 0 && (offset = strlen(card->content)) != 0) {
135             total += 1 + offset;
136             card->content = typeRealloc(char, total + 1, card->content);
137             if (card->content) {
138                 _nc_STRCPY(card->content + offset, " ", total + 1 - offset);
139                 offset++;
140             }
141         } else {
142             offset = 0;
143             if (card->content != 0)
144                 free(card->content);
145             card->content = typeMalloc(char, total + 1);
146         }
147         if (card->content)
148             _nc_STRCPY(card->content + offset, content, total + 1 - offset);
149         else
150             failed("add_content");
151     }
152 }
153
154 static CARD *
155 new_card(void)
156 {
157     CARD *card = add_title("");
158     add_content(card, "");
159     return card;
160 }
161
162 static CARD *
163 find_card(char *title)
164 {
165     CARD *card;
166
167     for (card = all_cards; card != 0; card = card->link)
168         if (!strcmp(card->title, title))
169             break;
170
171     return card;
172 }
173
174 static void
175 read_data(char *fname)
176 {
177     FILE *fp;
178
179     if ((fp = fopen(fname, "r")) != 0) {
180         CARD *card = 0;
181         char buffer[BUFSIZ];
182
183         while (fgets(buffer, sizeof(buffer), fp)) {
184             trim(buffer);
185             if (isspace(UChar(*buffer))) {
186                 if (card == 0)
187                     card = add_title("");
188                 add_content(card, buffer);
189             } else if ((card = find_card(buffer)) == 0) {
190                 card = add_title(buffer);
191             }
192         }
193         fclose(fp);
194     }
195 }
196
197 /*******************************************************************************/
198
199 static void
200 write_data(const char *fname)
201 {
202     FILE *fp;
203
204     if (!strcmp(fname, default_name))
205         fname = "cardfile.out";
206
207     if ((fp = fopen(fname, "w")) != 0) {
208         CARD *p = 0;
209
210         for (p = all_cards; p != 0; p = p->link) {
211             FIELD **f = form_fields(p->form);
212             int n;
213
214             for (n = 0; f[n] != 0; n++) {
215                 char *s = field_buffer(f[n], 0);
216                 if (s != 0
217                     && (s = strdup(s)) != 0) {
218                     trim(s);
219                     fprintf(fp, "%s%s\n", n ? "\t" : "", s);
220                     free(s);
221                 }
222             }
223         }
224         fclose(fp);
225     }
226 }
227
228 /*******************************************************************************/
229
230 /*
231  * Count the cards
232  */
233 static int
234 count_cards(void)
235 {
236     CARD *p;
237     int count = 0;
238
239     for (p = all_cards; p != 0; p = p->link)
240         count++;
241
242     return count;
243 }
244
245 /*
246  * Shuffle the panels to keep them in a natural hierarchy.
247  */
248 static void
249 order_cards(CARD * first, int depth)
250 {
251     if (first) {
252         if (depth && first->link)
253             order_cards(first->link, depth - 1);
254         if (isVisible(first))
255             top_panel(first->panel);
256     }
257 }
258
259 /*
260  * Return the next card in the list
261  */
262 static CARD *
263 next_card(CARD * now)
264 {
265     if (now->link != 0) {
266         CARD *tst = now->link;
267         if (isVisible(tst))
268             now = tst;
269         else
270             (void) next_card(tst);
271     }
272     return now;
273 }
274
275 /*
276  * Return the previous card in the list
277  */
278 static CARD *
279 prev_card(CARD * now)
280 {
281     CARD *p;
282     for (p = all_cards; p != 0; p = p->link) {
283         if (p->link == now) {
284             if (!isVisible(p))
285                 p = prev_card(p);
286             return p;
287         }
288     }
289     return now;
290 }
291
292 /*
293  * Returns the first card in the list that we will display.
294  */
295 static CARD *
296 first_card(CARD * now)
297 {
298     if (!isVisible(now))
299         now = next_card(now);
300     return now;
301 }
302
303 /*******************************************************************************/
304
305 static int
306 form_virtualize(WINDOW *w)
307 {
308     int c = wgetch(w);
309
310     switch (c) {
311     case CTRL('W'):
312         return (MY_CTRL_W);
313     case CTRL('N'):
314         return (MY_CTRL_N);
315     case CTRL('P'):
316         return (MY_CTRL_P);
317     case QUIT:
318     case ESCAPE:
319         return (MY_CTRL_Q);
320
321     case KEY_BACKSPACE:
322         return (REQ_DEL_PREV);
323     case KEY_DC:
324         return (REQ_DEL_CHAR);
325     case KEY_LEFT:
326         return (REQ_LEFT_CHAR);
327     case KEY_RIGHT:
328         return (REQ_RIGHT_CHAR);
329
330     case KEY_DOWN:
331     case KEY_NEXT:
332         return (REQ_NEXT_FIELD);
333     case KEY_UP:
334     case KEY_PREVIOUS:
335         return (REQ_PREV_FIELD);
336
337     default:
338         return (c);
339     }
340 }
341
342 static FIELD **
343 make_fields(CARD * p, int form_high, int form_wide)
344 {
345     FIELD **f = typeCalloc(FIELD *, (size_t) 3);
346
347     f[0] = new_field(1, form_wide, 0, 0, 0, 0);
348     set_field_back(f[0], A_REVERSE);
349     set_field_buffer(f[0], 0, p->title);
350     field_opts_off(f[0], O_BLANK);
351
352     f[1] = new_field(form_high - 1, form_wide, 1, 0, 0, 0);
353     set_field_buffer(f[1], 0, p->content);
354     set_field_just(f[1], JUSTIFY_LEFT);
355     field_opts_off(f[1], O_BLANK);
356
357     f[2] = 0;
358     return f;
359 }
360
361 static void
362 show_legend(void)
363 {
364     erase();
365     move(LINES - 3, 0);
366     addstr("^Q/ESC -- exit form            ^W   -- writes data to file\n");
367     addstr("^N   -- go to next card        ^P   -- go to previous card\n");
368     addstr("Arrow keys move left/right within a field, up/down between fields");
369 }
370
371 #if (defined(KEY_RESIZE) && HAVE_WRESIZE) || NO_LEAKS
372 static void
373 free_form_fields(FIELD **f)
374 {
375     int n;
376
377     for (n = 0; f[n] != 0; ++n) {
378         free_field(f[n]);
379     }
380     free(f);
381 }
382 #endif
383
384 /*******************************************************************************/
385
386 static void
387 cardfile(char *fname)
388 {
389     WINDOW *win;
390     CARD *p;
391     CARD *top_card;
392     int visible_cards;
393     int panel_wide;
394     int panel_high;
395     int form_wide;
396     int form_high;
397     int y;
398     int x;
399     int finished = FALSE;
400
401     show_legend();
402
403     /* decide how many cards we can display */
404     visible_cards = count_cards();
405     while (
406               (panel_wide = COLS - (visible_cards * OFFSET_CARD)) < 10 ||
407               (panel_high = LINES - (visible_cards * OFFSET_CARD) - 5) < 5) {
408         --visible_cards;
409     }
410     form_wide = panel_wide - 2;
411     form_high = panel_high - 2;
412     y = (visible_cards - 1) * OFFSET_CARD;
413     x = 0;
414
415     /* make a panel for each CARD */
416     for (p = all_cards; p != 0; p = p->link) {
417
418         if ((win = newwin(panel_high, panel_wide, y, x)) == 0)
419             break;
420
421         wbkgd(win, (chtype) COLOR_PAIR(pair_2));
422         keypad(win, TRUE);
423         p->panel = new_panel(win);
424         box(win, 0, 0);
425
426         p->form = new_form(make_fields(p, form_high, form_wide));
427         set_form_win(p->form, win);
428         set_form_sub(p->form, derwin(win, form_high, form_wide, 1, 1));
429         post_form(p->form);
430
431         y -= OFFSET_CARD;
432         x += OFFSET_CARD;
433     }
434
435     top_card = first_card(all_cards);
436     order_cards(top_card, visible_cards);
437
438     while (!finished) {
439         int ch = ERR;
440
441         update_panels();
442         doupdate();
443
444         ch = form_virtualize(panel_window(top_card->panel));
445         switch (form_driver(top_card->form, ch)) {
446         case E_OK:
447             break;
448         case E_UNKNOWN_COMMAND:
449             switch (ch) {
450             case MY_CTRL_Q:
451                 finished = TRUE;
452                 break;
453             case MY_CTRL_P:
454                 top_card = prev_card(top_card);
455                 order_cards(top_card, visible_cards);
456                 break;
457             case MY_CTRL_N:
458                 top_card = next_card(top_card);
459                 order_cards(top_card, visible_cards);
460                 break;
461             case MY_CTRL_W:
462                 form_driver(top_card->form, REQ_VALIDATION);
463                 write_data(fname);
464                 break;
465 #if defined(KEY_RESIZE) && HAVE_WRESIZE
466             case KEY_RESIZE:
467                 /* resizeterm already did "something" reasonable, but it cannot
468                  * know much about layout.  So let's make it nicer.
469                  */
470                 panel_wide = COLS - (visible_cards * OFFSET_CARD);
471                 panel_high = LINES - (visible_cards * OFFSET_CARD) - 5;
472
473                 form_wide = panel_wide - 2;
474                 form_high = panel_high - 2;
475
476                 y = (visible_cards - 1) * OFFSET_CARD;
477                 x = 0;
478
479                 show_legend();
480                 for (p = all_cards; p != 0; p = p->link) {
481                     FIELD **oldf = form_fields(p->form);
482                     WINDOW *olds = form_sub(p->form);
483
484                     if (!isVisible(p))
485                         continue;
486                     win = form_win(p->form);
487
488                     /* move and resize the card as needed
489                      * FIXME: if the windows are shrunk too much, this won't do
490                      */
491                     mvwin(win, y, x);
492                     wresize(win, panel_high, panel_wide);
493
494                     /* reconstruct each form.  Forms are not resizable, and
495                      * there appears to be no good way to reload the text in
496                      * a resized window.
497                      */
498                     werase(win);
499
500                     unpost_form(p->form);
501                     free_form(p->form);
502
503                     p->form = new_form(make_fields(p, form_high, form_wide));
504                     set_form_win(p->form, win);
505                     set_form_sub(p->form, derwin(win, form_high, form_wide,
506                                                  1, 1));
507                     post_form(p->form);
508
509                     free_form_fields(oldf);
510                     delwin(olds);
511
512                     box(win, 0, 0);
513
514                     y -= OFFSET_CARD;
515                     x += OFFSET_CARD;
516                 }
517                 break;
518 #endif
519             default:
520                 beep();
521                 break;
522             }
523             break;
524         default:
525             flash();
526             break;
527         }
528     }
529 #if NO_LEAKS
530     while (all_cards != 0) {
531         p = all_cards;
532         all_cards = all_cards->link;
533
534         if (isVisible(p)) {
535             FIELD **f = form_fields(p->form);
536
537             unpost_form(p->form);       /* ...so we can free it */
538             free_form(p->form); /* this also disconnects the fields */
539
540             free_form_fields(f);
541
542             del_panel(p->panel);
543         }
544         free(p->title);
545         free(p->content);
546         free(p);
547     }
548 #endif
549 }
550
551 static void
552 usage(void)
553 {
554     static const char *msg[] =
555     {
556         "Usage: cardfile [options] file"
557         ,""
558         ,"Options:"
559         ," -c       use color if terminal supports it"
560     };
561     size_t n;
562     for (n = 0; n < SIZEOF(msg); n++)
563         fprintf(stderr, "%s\n", msg[n]);
564     ExitProgram(EXIT_FAILURE);
565 }
566
567 /*******************************************************************************/
568
569 int
570 main(int argc, char *argv[])
571 {
572     int n;
573
574     setlocale(LC_ALL, "");
575
576     while ((n = getopt(argc, argv, "c")) != -1) {
577         switch (n) {
578         case 'c':
579             try_color = TRUE;
580             break;
581         default:
582             usage();
583         }
584     }
585
586     initscr();
587     cbreak();
588     noecho();
589
590     if (try_color) {
591         if (has_colors()) {
592             start_color();
593             init_pair(pair_1, COLOR_WHITE, COLOR_BLUE);
594             init_pair(pair_2, COLOR_WHITE, COLOR_CYAN);
595             bkgd((chtype) COLOR_PAIR(pair_1));
596         } else {
597             try_color = FALSE;
598         }
599     }
600
601     if (optind + 1 == argc) {
602         for (n = 1; n < argc; n++)
603             read_data(argv[n]);
604         if (count_cards() == 0)
605             new_card();
606         cardfile(argv[1]);
607     } else {
608         read_data(default_name);
609         if (count_cards() == 0)
610             new_card();
611         cardfile(default_name);
612     }
613
614     endwin();
615
616     ExitProgram(EXIT_SUCCESS);
617 }
618 #else
619 int
620 main(void)
621 {
622     printf("This program requires the curses form and panel libraries\n");
623     ExitProgram(EXIT_FAILURE);
624 }
625 #endif