scal.c (20010B)
1 #include <sys/param.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <pwd.h> 6 #include <stdlib.h> 7 #include <ctype.h> 8 #include <dirent.h> 9 #include <errno.h> 10 #include <time.h> 11 #include <curses.h> 12 13 #include "config.h" 14 15 #define ctrl(x) ((x) & 0x1f) 16 17 typedef enum {NO, YEARLY, MONTHLY, WEEKLY, DAILY} Recurring; 18 19 typedef struct 20 { 21 Recurring recurring; 22 struct tm time; 23 struct tm end; 24 char *description; 25 } Note ; 26 27 static void init_screen(void); 28 static void resize(void); 29 static void start(void); 30 static void print_cal(void); 31 static void write_notes(void); 32 static Note ** get_notes(void); 33 static Note * get_note(char *line_buf, ssize_t line_size); 34 static Recurring get_relevant_recur(struct tm *tm); 35 static void print_view(void); 36 static void print_month(const int monthadd, int *y); 37 static void combo_key(int keypress); 38 static void combo_go(int keypress); 39 static void combo_make(int keypress); 40 static void add_note(struct tm *tm); 41 static void edit_notes(void); 42 static void rm_note(struct tm *tm); 43 static int prompt_answer(char *buf, size_t size, const char *question, const char *mask, char *prefill); 44 static int get_curnotes(void); 45 static int get_note_view_col(Recurring recurring); 46 static int get_days(const struct tm *tm); 47 static int days_between(struct tm *tsa, struct tm *tsb); 48 static int is_relevant(Note *note, struct tm *a); 49 static int same_day(struct tm *a, struct tm *b); 50 static int compare_note(const void *a, const void *b); 51 static void move_page_up(void); 52 static void move_page_down(void); 53 static void move_up(void); 54 static void move_down(void); 55 static void move_right(void); 56 static void move_left(void); 57 static void curdate(void); 58 static char * cpstr(char *dest, const char *src); 59 static char * replace_digits(char *buf, const char *mask, int size); 60 static char * replace_home(char *buf, const char *path, const char *userhome); 61 static void free_notes(void); 62 static void clean(void); 63 static void fatal(const char *fmt, ...); 64 65 static char *MonthName[] = {"January","February","March","April","May","June","July","August", 66 "September","October","November","December"}; 67 static char *DayName[] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; 68 static struct tm curtm, highltm; 69 static WINDOW *wwin, *vwin; 70 static int maxy, maxx, wmaxy, wmaxx, vmaxy, vmaxx, highlight, note_count=0, curnote_count=0; 71 static Note **notes, **curnotes = NULL; 72 static char file[PATH_MAX], *editor; 73 74 int 75 main(int argc, char **argv) 76 { 77 char *userhome; 78 struct passwd userinf; 79 80 editor = getenv("EDITOR"); 81 82 userinf = *getpwuid(getuid()); 83 userhome = strdup(userinf.pw_dir); 84 85 replace_home(file, WORD_FILE, userhome); 86 87 get_notes(); 88 89 curdate(); 90 init_screen(); 91 92 print_cal(); 93 print_view(); 94 95 start(); 96 97 clean(); 98 } 99 100 void 101 init_screen(void) 102 { 103 initscr(); 104 noecho(); 105 start_color(); 106 use_default_colors(); 107 keypad(stdscr, TRUE); 108 curs_set(0); 109 110 clear(); 111 cbreak(); 112 113 init_pair(COLOR_BLACK, COLOR_BLACK, -1); 114 init_pair(COLOR_RED, COLOR_RED, -1); 115 init_pair(COLOR_GREEN,COLOR_GREEN, -1); 116 init_pair(COLOR_YELLOW, COLOR_YELLOW, -1); 117 init_pair(COLOR_BLUE, COLOR_BLUE, -1); 118 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1); 119 init_pair(COLOR_CYAN, COLOR_CYAN, -1); 120 init_pair(COLOR_WHITE, COLOR_WHITE, -1); 121 122 resize(); 123 } 124 125 void 126 resize(void) 127 { 128 int startx; 129 130 getmaxyx(stdscr, maxy, maxx); 131 132 wmaxy = maxy - BORDER_SPACE_SIZE; 133 wmaxx = maxx/5 - BORDER_SPACE_SIZE > 0 ? maxx/5 - BORDER_SPACE_SIZE : 1; 134 startx = maxx > BORDER_SPACE_SIZE ? BORDER_SPACE_SIZE : 0; 135 wwin = newwin(wmaxy, wmaxx, BORDER_SPACE_SIZE, startx); 136 137 vmaxy = maxy - BORDER_SPACE_SIZE; 138 vmaxx = maxx - wmaxx - BORDER_SPACE_SIZE; 139 startx = wmaxx + BORDER_SPACE_SIZE; 140 vwin = newwin(wmaxy, vmaxx, BORDER_SPACE_SIZE, startx); 141 142 clear(); 143 refresh(); 144 } 145 146 void 147 print_cal(void) 148 { 149 int y = 0; 150 151 wclear(wwin); 152 wclear(vwin); 153 154 attron(COLOR_PAIR(DAY_TITLE_COLOR)); 155 mvprintw(0, 0, "%s %d %s %d\n", 156 DayName[highltm.tm_wday], 157 highltm.tm_mday, 158 MonthName[highltm.tm_mon], 159 highltm.tm_year + 1900); 160 attroff(COLOR_PAIR(DAY_TITLE_COLOR)); 161 162 print_month(-1, &y); 163 print_month(0, &y); 164 print_month(1, &y); 165 166 wrefresh(wwin); 167 } 168 169 void 170 print_month(const int monthadd, int *y) 171 { 172 int wcount, today, wnote, sindex, color, x = 0; 173 Recurring notecol; 174 struct tm otm = highltm; 175 176 if (monthadd >= 0) 177 (*y)+=2; 178 otm.tm_mon += monthadd; 179 otm.tm_mday = 1; 180 timegm(&otm); 181 wcount = get_days(&otm); 182 183 wattron(wwin, COLOR_PAIR(MONTH_TITLE_COLOR)); 184 mvwprintw(wwin, *y,0,"%s %d\n", 185 MonthName[otm.tm_mon], 186 otm.tm_year + 1900); 187 wattroff(wwin, COLOR_PAIR(MONTH_TITLE_COLOR)); 188 mvwprintw(wwin, (*y)+=2 ,0,"%s\n", "Su Mo Tu We Th Fr Sa"); 189 190 (*y)+=1; 191 x=otm.tm_wday*3; 192 193 for (size_t i = 0; i < wcount; ++i) { 194 otm.tm_mday=i+1; 195 today = same_day(&otm, &curtm); 196 notecol = get_relevant_recur(&otm); 197 198 if (today) 199 wattron(wwin, A_UNDERLINE); 200 if (wnote = notecol != -1) { 201 color = get_note_view_col(notecol); 202 wattron(wwin, COLOR_PAIR(color)); 203 } 204 if (monthadd == 0 && highlight == i) { 205 wattron(wwin, A_REVERSE); 206 mvwprintw(wwin, *y, x, "%d", otm.tm_mday); 207 wattroff(wwin, A_REVERSE); 208 } else 209 mvwprintw(wwin, *y, x, "%d", otm.tm_mday); 210 if (today) 211 wattroff(wwin, A_UNDERLINE); 212 if (wnote) 213 wattroff(wwin, COLOR_PAIR(color)); 214 x+=3; 215 if (x % DAY_COLUMN_SIZE == 0 && i != wcount-1) { 216 (*y)++; 217 x = 0; 218 } 219 } 220 } 221 222 void 223 start(void) 224 { 225 int c; 226 227 while ((c = getch()) != KEY_QUIT) { 228 switch (c) { 229 case KEY_UP: 230 case KEY_VUP: 231 move_up(); 232 break; 233 case KEY_DOWN: 234 case KEY_VDOWN: 235 move_down(); 236 break; 237 case 10: 238 print_view(); 239 break; 240 case KEY_RIGHT: 241 case KEY_VRIGHT: 242 case KEY_VRIGHT_ABS: 243 move_right(); 244 break; 245 case KEY_LEFT: 246 case KEY_VLEFT: 247 case KEY_VLEFT_ABS: 248 move_left(); 249 break; 250 case KEY_PPAGE: 251 case ctrl('u'): 252 move_page_up(); 253 break; 254 case KEY_NPAGE: 255 case ctrl('d'): 256 move_page_down(); 257 break; 258 case KEY_EDIT_NOTES: 259 edit_notes(); 260 break; 261 case KEY_COMBO_GO: 262 case KEY_COMBO_MAKE: 263 combo_key(c); 264 break; 265 case KEY_RESIZE: 266 resize(); 267 break; 268 default: 269 break; 270 } 271 print_cal(); 272 print_view(); 273 } 274 } 275 276 void 277 combo_key(int keypress) 278 { 279 int c; 280 281 move(maxy-1, maxx-1); 282 clrtoeol(); 283 addch(keypress); 284 285 halfdelay(10); 286 287 while ((c = getch()) != ERR) { 288 switch (keypress) { 289 case KEY_COMBO_GO: 290 combo_go(c); 291 break; 292 case KEY_COMBO_MAKE: 293 combo_make(c); 294 break; 295 default: 296 break; 297 } 298 break; 299 } 300 301 cbreak(); 302 move(maxy-1, maxx-1); 303 clrtoeol(); 304 } 305 306 void 307 combo_go(int key) 308 { 309 switch (key) { 310 case KEY_COMBO_GO_TODAY: 311 curdate(); 312 break; 313 default: 314 break; 315 } 316 } 317 318 void 319 combo_make(int key) 320 { 321 cbreak(); 322 323 switch (key) { 324 case KEY_COMBO_MAKE_ADD: 325 add_note(&highltm); 326 break; 327 case KEY_COMBO_MAKE_RM: 328 rm_note(&highltm); 329 break; 330 default: 331 break; 332 } 333 } 334 335 void 336 add_note(struct tm *tm) { 337 size_t len; 338 char answer[DESCLEN+20], buf[DESCLEN], rec[2], time[6], date[11], prefill[11], span[2]; 339 char end[11] = "2999-12-31\0"; 340 341 if (tm != NULL) 342 snprintf(prefill, 11, "%04d-%02d-%02d\0", 343 tm->tm_year+1900, 344 tm->tm_mon+1, 345 tm->tm_mday); 346 347 if (prompt_answer(rec, 1, "Recurring? (0=NO;1=YEARLY;2=MONTHLY;3=WEEKLY;4=DAILY)", "4", "0")) 348 return; 349 if (prompt_answer(date, 10, "Date?", "2999-19-39", prefill)) 350 return; 351 if (prompt_answer(time, 5, "Time?", "29:59", NULL)) 352 return; 353 354 if (rec[0] != '0') { 355 if (prompt_answer(span, 1, "Set end date? (0=NO;1=YES)", "1", "0")) 356 return; 357 if (span[0] == '1') { 358 if (prompt_answer(end, 10, "End date?", "2999-19-39", NULL)) 359 return; 360 } 361 } 362 363 if (prompt_answer(buf, DESCLEN, "Description?", NULL, NULL)) 364 return; 365 366 snprintf(answer, DESCLEN+20, "%s|%10sT%s|%10s|%s", rec, date, time, end, buf); 367 368 len = strlen(answer); 369 370 if (len > sizeof(rec) + sizeof(date) + sizeof(end) + sizeof(time)) { 371 notes = (Note**) realloc(notes, (sizeof(Note*) * (note_count + 1))); 372 if (notes == NULL) 373 fatal("Fatal: failed to reallocate bytes for notes.\n"); 374 375 notes[note_count] = get_note(answer, len); 376 timegm(¬es[note_count]->time); 377 note_count++; 378 write_notes(); 379 } 380 } 381 382 void 383 edit_notes(void) 384 { 385 if (editor == NULL) 386 fatal("no editor found; set EDITOR environment variable."); 387 388 sigset_t set; 389 char cmd[PATH_MAX + strlen(editor) + 4]; 390 391 endwin(); 392 393 if (sigemptyset (&set) == -1) 394 fatal("Sigemptyset failed."); 395 if (sigaddset(&set, SIGWINCH) == -1) 396 fatal("Sigaddset failed."); 397 if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) 398 fatal("Blocking sigprocmask failed."); 399 400 sprintf(cmd, "%s \"%s\"", editor, file); 401 system(cmd); 402 403 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) 404 fatal("Unblocking sigprocmask failed."); 405 406 clear(); 407 noecho(); 408 cbreak(); 409 410 free_notes(); 411 get_notes(); 412 refresh(); 413 } 414 415 void 416 rm_note(struct tm *tm) { 417 int c, y, x; 418 419 if (curnote_count == 0) 420 return; 421 422 move(BORDER_SPACE_SIZE, wmaxx + BORDER_SPACE_SIZE - 1); 423 curs_set(1); 424 getyx(stdscr, y, x); 425 426 while ((c = wgetch(stdscr)) != 10) { 427 switch (c) { 428 case KEY_BACKSPACE: 429 curs_set(0); 430 return; 431 case KEY_UP: 432 case KEY_VUP: 433 if (y-1 >= BORDER_SPACE_SIZE) 434 move(--y, x); 435 break; 436 case KEY_DOWN: 437 case KEY_VDOWN: 438 if (y+1 < BORDER_SPACE_SIZE + curnote_count) 439 move(++y, x); 440 break; 441 } 442 } 443 int found = 0; 444 for (int i = 0; i < note_count; i++) { 445 if (notes[i] == curnotes[y-BORDER_SPACE_SIZE]) { 446 found = 1; 447 free(notes[i]->description); 448 free(notes[i]); 449 } else if (found) 450 notes[i-1] = notes[i]; 451 } 452 note_count--; 453 write_notes(); 454 curs_set(0); 455 } 456 457 void 458 write_notes(void) 459 { 460 FILE *fp = fopen(file, "w+"); 461 462 if (!fp) 463 fatal("Error opening file '%s'\n", file); 464 465 qsort(notes, note_count, sizeof(Note*), compare_note); 466 467 for (int i = 0; i < note_count; i++) 468 fprintf(fp, "%d|%04d-%02d-%02dT%02d:%02d|%04d-%02d-%02d|%s\n", 469 notes[i]->recurring, 470 notes[i]->time.tm_year+1900, 471 notes[i]->time.tm_mon+1, 472 notes[i]->time.tm_mday, 473 notes[i]->time.tm_hour, 474 notes[i]->time.tm_min, 475 notes[i]->end.tm_year+1900, 476 notes[i]->end.tm_mon+1, 477 notes[i]->end.tm_mday, 478 notes[i]->description); 479 fclose(fp); 480 } 481 482 Note ** 483 get_notes(void) 484 { 485 char *line_buf = NULL; 486 size_t line_buf_size = 0; 487 ssize_t line_size; 488 489 FILE *fp = fopen(file, "r"); 490 491 if (!fp) 492 fatal("Error opening file '%s'\n", file); 493 494 line_size = getline(&line_buf, &line_buf_size, fp); 495 notes = (Note**) malloc(sizeof(Note*)); 496 497 if (notes == NULL) 498 fatal("Fatal: failed to allocate bytes for notes.\n"); 499 500 while (line_size >= 0) { 501 notes = (Note**) realloc(notes, (sizeof(Note*) * (note_count + 1))); 502 if (notes == NULL) 503 fatal("Fatal: failed to reallocate bytes for notes.\n"); 504 505 notes[note_count] = get_note(line_buf, line_size); 506 507 line_size = getline(&line_buf, &line_buf_size, fp); 508 note_count++; 509 } 510 511 free(line_buf); 512 fclose(fp); 513 514 write_notes(); 515 516 return notes; 517 } 518 519 Note * 520 get_note(char *line_buf, ssize_t line_size) 521 { 522 char timestr[19], enddate[11]; 523 Note *note = (Note*) calloc(1, sizeof(Note)); 524 525 if (note == NULL) 526 fatal("Fatal: failed to allocate bytes for note.\n"); 527 528 note->description = (char*) malloc(line_size); 529 530 if (note->description == NULL) 531 fatal("Fatal: failed to allocate bytes for note.\n"); 532 533 sscanf(line_buf, "%d|%[^|]|%[^|]|%[^\n]", 534 ¬e->recurring, 535 timestr, 536 enddate, 537 note->description); 538 539 note->description[line_size-1] = '\0'; 540 541 if (sscanf(timestr, "%d-%d-%dT%d:%d", 542 &(note->time.tm_year), 543 &(note->time.tm_mon), 544 &(note->time.tm_mday), 545 &(note->time.tm_hour), 546 &(note->time.tm_min)) >= 3) { 547 note->time.tm_year-=1900; 548 note->time.tm_mon-=1; 549 } 550 if (sscanf(enddate, "%d-%d-%d", 551 &(note->end.tm_year), 552 &(note->end.tm_mon), 553 &(note->end.tm_mday)) >= 3) { 554 note->end.tm_year-=1900; 555 note->end.tm_mon-=1; 556 } 557 558 return note; 559 } 560 561 Recurring 562 get_relevant_recur(struct tm *tm) 563 { 564 Recurring relevant = -1; 565 566 for (int i = 0; i < note_count; i++) { 567 if (is_relevant(notes[i], tm)) 568 relevant = MIN(notes[i]->recurring, relevant == -1 ? 4 : relevant); 569 } 570 571 return relevant; 572 } 573 574 void 575 print_view(void) 576 { 577 int y = -1, x = 0, color; 578 579 get_curnotes(); 580 581 for (int i = 0; i < curnote_count; i++) { 582 y++; 583 if ((color = get_note_view_col(curnotes[i]->recurring)) > -1) { 584 wattron(vwin, COLOR_PAIR(color)); 585 mvwaddch(vwin, y, x, '|'); 586 wattroff(vwin, COLOR_PAIR(color)); 587 } 588 mvwprintw(vwin, y, x+1, "%02d:%02d %s\n", 589 curnotes[i]->time.tm_hour, 590 curnotes[i]->time.tm_min, 591 curnotes[i]->description); 592 } 593 wrefresh(vwin); 594 } 595 596 int 597 prompt_answer(char *buf, size_t size, const char *question, const char *mask, char *prefill) 598 { 599 int c, y, x, nr, phlen = mask == NULL ? 0 : strlen(mask), status = 0; 600 char placeholder[phlen]; 601 char *startfill; 602 size_t len = 0; 603 604 replace_digits(placeholder, mask, phlen); 605 606 move(maxy-1, 1); 607 clrtoeol(); 608 609 attron(COLOR_PAIR(PROMPT_COLOR)); 610 addstr(question); 611 attroff(COLOR_PAIR(PROMPT_COLOR)); 612 if (prefill != NULL) 613 startfill = prefill; 614 else if (placeholder != NULL) 615 startfill = placeholder; 616 else 617 startfill = ""; 618 619 printw(" %s", startfill); 620 621 if (prefill != NULL) { 622 len = strlen(prefill); 623 cpstr(buf, prefill); 624 } 625 626 move(maxy-1, strlen(question)+2 + len); 627 628 echo(); 629 curs_set(1); 630 631 getyx(stdscr, y, x); 632 633 while ((c = wgetch(stdscr)) != 10) { 634 move(maxy-2, 1); 635 clrtoeol(); 636 move(y,x); 637 switch (c) { 638 case KEY_BACKSPACE: 639 if (len > 0) { 640 if (len <= phlen) { 641 buf[--(len)] = placeholder[len]; 642 move(y, --x); 643 attron(COLOR_PAIR(PROMPT_COLOR)); 644 addch(buf[len]); 645 attroff(COLOR_PAIR(PROMPT_COLOR)); 646 move(y, x); 647 if (len > 0 && len <= phlen && placeholder[len] != '_') { 648 buf[--(len)] = placeholder[len]; 649 move(y, --x); 650 attron(COLOR_PAIR(PROMPT_COLOR)); 651 addch(buf[len]); 652 attroff(COLOR_PAIR(PROMPT_COLOR)); 653 move(y, x); 654 } 655 } else { 656 buf[--(len)] = '\0'; 657 move(y, --x); 658 clrtoeol(); 659 } 660 } 661 break; 662 default: 663 if (len < size) { 664 if (len < phlen) { 665 if (!isdigit(c) || (nr = mask[len] - '0') < c - '0') { 666 mvprintw(maxy-2, 1, "Only digits (max: %d)", nr); 667 move(y,x); 668 break; 669 } 670 buf[(len)++] = c; 671 move(y, ++x); 672 if (len < phlen && placeholder[len] != '_') { 673 buf[len] = placeholder[len]; 674 len++; 675 move(y, ++x); 676 } 677 } else { 678 buf[(len)++] = c; 679 move(y, ++x); 680 } 681 } else 682 clrtoeol(); 683 break; 684 } 685 } 686 noecho(); 687 curs_set(0); 688 689 if (strchr(buf, '_') || len < phlen) { 690 buf[0] = '\0'; 691 status = 1; 692 } else 693 buf[phlen > 0 ? phlen : len] = '\0'; 694 695 move(maxy-1, 1); 696 clrtoeol(); 697 move(maxy-2, 1); 698 clrtoeol(); 699 700 return status; 701 } 702 703 int 704 get_curnotes(void) 705 { 706 if (curnote_count > 0) { 707 curnote_count = 0; 708 free(curnotes); 709 curnotes = NULL; 710 } 711 712 curnotes = (Note**) malloc(sizeof(Note*)); 713 714 if (curnotes == NULL) 715 fatal("Fatal: failed to allocate bytes for notes.\n"); 716 717 for (int i = 0; i < note_count; i++) { 718 if (is_relevant(notes[i], &highltm)) { 719 curnotes = (Note**) realloc(curnotes, (sizeof(Note*) * (curnote_count + 1))); 720 if (curnotes == NULL) 721 fatal("Fatal: failed to reallocate bytes for notes.\n"); 722 curnotes[curnote_count] = notes[i]; 723 curnote_count++; 724 } 725 } 726 } 727 728 int 729 get_note_view_col(Recurring recurring) 730 { 731 switch (recurring) { 732 case DAILY: 733 return DAILY_VIEW_COLOR; 734 case WEEKLY: 735 return WEEKLY_VIEW_COLOR; 736 case MONTHLY: 737 return MONTHLY_VIEW_COLOR; 738 case YEARLY: 739 return YEARLY_VIEW_COLOR; 740 case NO: 741 return NOTED_VIEW_COLOR; 742 default: 743 return -1; 744 } 745 } 746 747 int 748 get_days(const struct tm *tm) 749 { 750 struct tm start = *tm, end = *tm; 751 752 start.tm_mday=1; 753 end.tm_mon = start.tm_mon+1; 754 end.tm_mday = 1; 755 756 return days_between(&start, &end); 757 } 758 759 int 760 days_between(struct tm *tsa, struct tm *tsb) 761 { 762 time_t a = timegm(tsa); 763 time_t b = timegm(tsb); 764 765 return (b - a)/(60*60*24); 766 } 767 768 int 769 is_relevant(Note *note, struct tm *a) 770 { 771 timegm(¬e->time); 772 timegm(¬e->end); 773 timegm(a); 774 775 if ((note->end.tm_year > a->tm_year 776 || (note->end.tm_yday >= a->tm_yday && note->end.tm_year == a->tm_year)) 777 && (note->time.tm_year < a->tm_year 778 || (note->time.tm_yday <= a->tm_yday && note->time.tm_year == a->tm_year))) { 779 switch (note->recurring) { 780 case DAILY: 781 return note->time.tm_yday <= a->tm_yday || note->end.tm_year > a->tm_year; 782 case WEEKLY: 783 return note->time.tm_wday == a->tm_wday; 784 case MONTHLY: 785 return note->time.tm_mday == a->tm_mday; 786 case YEARLY: 787 return note->time.tm_mday == a->tm_mday && note->time.tm_mon == a->tm_mon; 788 case NO: 789 return note->time.tm_yday == a->tm_yday && note->time.tm_year == a->tm_year; 790 default: 791 return 0; 792 } 793 } 794 return 0; 795 } 796 797 int 798 same_day(struct tm *a, struct tm *b) 799 { 800 if (a == NULL || b == NULL) 801 return 0; 802 803 timegm(a); 804 timegm(b); 805 806 return a->tm_yday == b->tm_yday && a->tm_year == b->tm_year; 807 } 808 809 int 810 compare_note(const void *a, const void *b) 811 { 812 const Note *ma = *(Note**)a; 813 const Note *mb = *(Note**)b; 814 815 if (ma == NULL || mb == NULL) 816 return 0; 817 818 if (ma->recurring == mb->recurring) { 819 if (ma->time.tm_year != mb->time.tm_year) 820 return ma->time.tm_year > mb->time.tm_year; 821 if (ma->time.tm_mon != mb->time.tm_mon) 822 return ma->time.tm_mon > mb->time.tm_mon; 823 if (ma->time.tm_mday != mb->time.tm_mday) 824 return ma->time.tm_mday > mb->time.tm_mday; 825 } 826 if (ma->time.tm_hour != mb->time.tm_hour) 827 return ma->time.tm_hour > mb->time.tm_hour; 828 if (ma->time.tm_min != mb->time.tm_min) 829 return ma->time.tm_min > mb->time.tm_min; 830 831 return 0; 832 } 833 834 void 835 move_page_up(void) 836 { 837 highlight = 0; 838 highltm.tm_mday = highlight+1; 839 highltm.tm_mon -= 1; 840 timegm(&highltm); 841 } 842 843 void 844 move_page_down(void) 845 { 846 highlight = 0; 847 highltm.tm_mday = highlight+1; 848 highltm.tm_mon += 1; 849 timegm(&highltm); 850 } 851 852 void 853 move_up(void) 854 { 855 if (highlight > 0) { 856 highlight = MAX(highlight-DAY_COLUMN_SIZE, 0); 857 highltm.tm_mday = highlight+1; 858 timegm(&highltm); 859 } else if (highlight == 0){ 860 highltm.tm_mday -= 1; 861 timegm(&highltm); 862 highlight = highltm.tm_mday -1; 863 } 864 } 865 866 void 867 move_down(void) 868 { 869 int wcount = get_days(&highltm); 870 871 if (highlight < wcount-DAY_COLUMN_SIZE) { 872 highlight = MIN(highlight+DAY_COLUMN_SIZE, wcount-1); 873 highltm.tm_mday = highlight+1; 874 timegm(&highltm); 875 } else if (highlight < wcount-1) { 876 highlight = wcount-1; 877 highltm.tm_mday = highlight+1; 878 timegm(&highltm); 879 } else if (highlight == wcount-1){ 880 highlight = 0; 881 highltm.tm_mday = highlight+1; 882 highltm.tm_mon += 1; 883 timegm(&highltm); 884 } 885 } 886 887 void 888 move_right(void) 889 { 890 int wcount = get_days(&highltm); 891 892 if (highlight < wcount-1) { 893 highlight++; 894 highltm.tm_mday = highlight+1; 895 timegm(&highltm); 896 } else if (highlight == wcount-1){ 897 highlight = 0; 898 highltm.tm_mday = highlight+1; 899 highltm.tm_mon += 1; 900 timegm(&highltm); 901 } 902 } 903 904 void 905 move_left(void) 906 { 907 if (highlight > 0) { 908 highlight--; 909 highltm.tm_mday = highlight+1; 910 timegm(&highltm); 911 } else if (highlight == 0){ 912 highltm.tm_mday -= 1; 913 timegm(&highltm); 914 highlight = highltm.tm_mday -1; 915 } 916 } 917 918 char * 919 cpstr(char *dest, const char *src) 920 { 921 size_t cplen = strlen(src); 922 923 memcpy(dest, src, cplen); 924 dest[cplen] = '\0'; 925 return dest; 926 } 927 928 char * 929 replace_digits(char *buf, const char *mask, int size) { 930 for (int i = 0; i < size; i++) 931 buf[i] = isdigit(mask[i]) ? '_' : mask[i] ; 932 buf[size] = '\0'; 933 return buf; 934 } 935 936 char * 937 replace_home(char *buf, const char *path, const char *userhome) 938 { 939 char *envv = "$HOME"; 940 941 if ((strstr(path, envv) != NULL)) { 942 cpstr(buf, userhome); 943 strcat(buf, path + strlen(envv)); 944 } else 945 cpstr(buf, path); 946 return buf; 947 } 948 949 void 950 curdate(void) 951 { 952 time_t t = time(NULL); 953 954 curtm = *gmtime(&t); 955 highltm = curtm; 956 highlight = highltm.tm_mday-1; 957 } 958 959 void 960 free_notes(void) 961 { 962 for (int i = 0; i < note_count; i++) { 963 free(notes[i]->description); 964 free(notes[i]); 965 } 966 if (notes != NULL) 967 free(notes); 968 969 if (curnotes != NULL) 970 free(curnotes); 971 972 notes = NULL; 973 curnotes = NULL; 974 note_count = 0; 975 curnote_count = 0; 976 } 977 978 void 979 clean(void) 980 { 981 clear(); 982 983 delwin(wwin); 984 delwin(vwin); 985 delwin(stdscr); 986 987 endwin(); 988 free_notes(); 989 } 990 991 void 992 fatal(const char *fmt, ...) 993 { 994 va_list ap; 995 996 va_start(ap, fmt); 997 vfprintf(stderr, fmt, ap); 998 va_end(ap); 999 1000 clean(); 1001 1002 exit(EXIT_FAILURE); 1003 }