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