1/****************************************************************************
2 * Copyright (c) 1999-2003,2004 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.27 2004/11/06 19:33:39 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
51enum {
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
59typedef struct _card {
60    struct _card *link;
61    PANEL *panel;
62    FORM *form;
63    char *title;
64    char *content;
65} CARD;
66
67static CARD *all_cards;
68static bool try_color = FALSE;
69static char default_name[] = "cardfile.dat";
70
71#if !HAVE_STRDUP
72#define strdup my_strdup
73static char *
74strdup(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
83static const char *
84skip(const char *buffer)
85{
86    while (isspace(UChar(*buffer)))
87	buffer++;
88    return buffer;
89}
90
91static void
92trim(char *buffer)
93{
94    unsigned n = strlen(buffer);
95    while (n-- && isspace(UChar(buffer[n])))
96	buffer[n] = 0;
97}
98
99/*******************************************************************************/
100
101static CARD *
102add_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
129static void
130add_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	    strcpy(card->content + offset++, " ");
140	} else {
141	    if (card->content != 0)
142		free(card->content);
143	    card->content = (char *) malloc(total + 1);
144	}
145	strcpy(card->content + offset, content);
146    }
147}
148
149static CARD *
150new_card(void)
151{
152    CARD *card = add_title("");
153    add_content(card, "");
154    return card;
155}
156
157static CARD *
158find_card(char *title)
159{
160    CARD *card;
161
162    for (card = all_cards; card != 0; card = card->link)
163	if (!strcmp(card->title, title))
164	    break;
165
166    return card;
167}
168
169static void
170read_data(char *fname)
171{
172    FILE *fp;
173    CARD *card = 0;
174    char buffer[BUFSIZ];
175
176    if ((fp = fopen(fname, "r")) != 0) {
177	while (fgets(buffer, sizeof(buffer), fp)) {
178	    trim(buffer);
179	    if (isspace(UChar(*buffer))) {
180		if (card == 0)
181		    card = add_title("");
182		add_content(card, buffer);
183	    } else if ((card = find_card(buffer)) == 0) {
184		card = add_title(buffer);
185	    }
186	}
187	fclose(fp);
188    }
189}
190
191/*******************************************************************************/
192
193static void
194write_data(const char *fname)
195{
196    FILE *fp;
197    CARD *p = 0;
198    int n;
199
200    if (!strcmp(fname, default_name))
201	fname = "cardfile.out";
202
203    if ((fp = fopen(fname, "w")) != 0) {
204	for (p = all_cards; p != 0; p = p->link) {
205	    FIELD **f = form_fields(p->form);
206	    for (n = 0; f[n] != 0; n++) {
207		char *s = field_buffer(f[n], 0);
208		if (s != 0
209		    && (s = strdup(s)) != 0) {
210		    trim(s);
211		    fprintf(fp, "%s%s\n", n ? "\t" : "", s);
212		    free(s);
213		}
214	    }
215	}
216	fclose(fp);
217    }
218}
219
220/*******************************************************************************/
221
222/*
223 * Count the cards
224 */
225static int
226count_cards(void)
227{
228    CARD *p;
229    int count = 0;
230
231    for (p = all_cards; p != 0; p = p->link)
232	count++;
233
234    return count;
235}
236
237/*
238 * Shuffle the panels to keep them in a natural hierarchy.
239 */
240static void
241order_cards(CARD * first, int depth)
242{
243    if (first) {
244	if (depth && first->link)
245	    order_cards(first->link, depth - 1);
246	if (isVisible(first))
247	    top_panel(first->panel);
248    }
249}
250
251/*
252 * Return the next card in the list
253 */
254static CARD *
255next_card(CARD * now)
256{
257    if (now->link != 0) {
258	CARD *tst = now->link;
259	if (isVisible(tst))
260	    now = tst;
261	else
262	    tst = next_card(tst);
263    }
264    return now;
265}
266
267/*
268 * Return the previous card in the list
269 */
270static CARD *
271prev_card(CARD * now)
272{
273    CARD *p;
274    for (p = all_cards; p != 0; p = p->link) {
275	if (p->link == now) {
276	    if (!isVisible(p))
277		p = prev_card(p);
278	    return p;
279	}
280    }
281    return now;
282}
283
284/*
285 * Returns the first card in the list that we will display.
286 */
287static CARD *
288first_card(CARD * now)
289{
290    if (!isVisible(now))
291	now = next_card(now);
292    return now;
293}
294
295/*******************************************************************************/
296
297static int
298form_virtualize(WINDOW *w)
299{
300    int c = wgetch(w);
301
302    switch (c) {
303    case CTRL('W'):
304	return (MY_CTRL_W);
305    case CTRL('N'):
306	return (MY_CTRL_N);
307    case CTRL('P'):
308	return (MY_CTRL_P);
309    case CTRL('Q'):
310    case 033:
311	return (MY_CTRL_Q);
312
313    case KEY_BACKSPACE:
314	return (REQ_DEL_PREV);
315    case KEY_DC:
316	return (REQ_DEL_CHAR);
317    case KEY_LEFT:
318	return (REQ_LEFT_CHAR);
319    case KEY_RIGHT:
320	return (REQ_RIGHT_CHAR);
321
322    case KEY_DOWN:
323    case KEY_NEXT:
324	return (REQ_NEXT_FIELD);
325    case KEY_UP:
326    case KEY_PREVIOUS:
327	return (REQ_PREV_FIELD);
328
329    default:
330	return (c);
331    }
332}
333
334static FIELD **
335make_fields(CARD * p, int form_high, int form_wide)
336{
337    FIELD **f = (FIELD **) calloc(3, sizeof(FIELD *));
338
339    f[0] = new_field(1, form_wide, 0, 0, 0, 0);
340    set_field_back(f[0], A_REVERSE);
341    set_field_buffer(f[0], 0, p->title);
342    field_opts_off(f[0], O_BLANK);
343
344    f[1] = new_field(form_high - 1, form_wide, 1, 0, 0, 0);
345    set_field_buffer(f[1], 0, p->content);
346    set_field_just(f[1], JUSTIFY_LEFT);
347    field_opts_off(f[1], O_BLANK);
348
349    f[2] = 0;
350    return f;
351}
352
353static void
354show_legend(void)
355{
356    erase();
357    move(LINES - 3, 0);
358    addstr("^Q/ESC -- exit form            ^W   -- writes data to file\n");
359    addstr("^N   -- go to next card        ^P   -- go to previous card\n");
360    addstr("Arrow keys move left/right within a field, up/down between fields");
361}
362
363#if (defined(KEY_RESIZE) && HAVE_WRESIZE) || NO_LEAKS
364static void
365free_form_fields(FIELD ** f)
366{
367    int n;
368
369    for (n = 0; f[n] != 0; ++n) {
370	free_field(f[n]);
371    }
372    free(f);
373}
374#endif
375
376/*******************************************************************************/
377
378static void
379cardfile(char *fname)
380{
381    WINDOW *win;
382    CARD *p;
383    CARD *top_card;
384    int visible_cards;
385    int panel_wide;
386    int panel_high;
387    int form_wide;
388    int form_high;
389    int y;
390    int x;
391    int ch = ERR;
392    int last_ch;
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, 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	last_ch = ch;
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
548static void
549usage(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
566int
567main(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
616int
617main(void)
618{
619    printf("This program requires the curses form and panel libraries\n");
620    ExitProgram(EXIT_FAILURE);
621}
622#endif
623