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