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