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