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