scal.c (20039B)
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 #define arrlen(arr) (sizeof(arr)/sizeof(0[arr])) 17 18 typedef enum {NO, YEARLY, MONTHLY, WEEKLY, DAILY} Recurring; 19 20 typedef struct 21 { 22 Recurring recurring; 23 struct tm time; 24 struct tm end; 25 char *description; 26 } Note; 27 28 static void init_screen(void); 29 static void resize(void); 30 static void start(void); 31 static void print_cal(void); 32 static void write_notes(void); 33 static Note ** get_notes(void); 34 static Note * get_note(char *line_buf, ssize_t line_size); 35 static int get_relevant_recur(struct tm *tm); 36 static void print_view(void); 37 static void print_month(const int monthadd, int *y); 38 static void combo_key(int keypress); 39 static void combo_go(int keypress); 40 static void combo_make(int keypress); 41 static void add_note(struct tm *tm); 42 static void edit_notes(void); 43 static int prompt_answer(char *buf, int size, const char *question, const char *mask, char *prefill); 44 static void rm_note(void); 45 static int get_curnotes(void); 46 static int get_note_view_col(Recurring recurring); 47 static int get_days(const struct tm *tm); 48 static int days_between(struct tm *tsa, struct tm *tsb); 49 static int is_relevant(Note *note, struct tm *a); 50 static int same_day(struct tm *a, struct tm *b); 51 static int compare_note(const void *a, const void *b); 52 static void move_page_up(void); 53 static void move_page_down(void); 54 static void move_up(void); 55 static void move_down(void); 56 static void move_right(void); 57 static void move_left(void); 58 static void curdate(void); 59 static char * cpstr(char *dest, const char *src); 60 static char * replace_digits(char *buf, const char *mask, int size); 61 static char * replace_home(char *buf, const char *path, const char *userhome); 62 static void free_notes(void); 63 static void clean(void); 64 static void fatal(const char *fmt, ...); 65 66 static char *MonthName[] = {"January","February","March","April","May","June","July","August", 67 "September","October","November","December"}; 68 static char *DayName[] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; 69 static struct tm curtm, highltm; 70 static WINDOW *wwin, *vwin; 71 static int maxy, maxx, wmaxy, wmaxx, vmaxy, vmaxx, highlight, note_count=0, curnote_count=0; 72 static Note **notes, **curnotes = NULL; 73 static char file[PATH_MAX], *editor; 74 75 int 76 main(void) 77 { 78 char *userhome; 79 struct passwd userinf; 80 81 editor = getenv("EDITOR"); 82 83 userinf = *getpwuid(getuid()); 84 userhome = strdup(userinf.pw_dir); 85 86 replace_home(file, WORD_FILE, userhome); 87 88 get_notes(); 89 90 curdate(); 91 init_screen(); 92 93 print_cal(); 94 print_view(); 95 96 start(); 97 98 clean(); 99 } 100 101 void 102 init_screen(void) 103 { 104 initscr(); 105 noecho(); 106 start_color(); 107 use_default_colors(); 108 keypad(stdscr, TRUE); 109 curs_set(0); 110 111 clear(); 112 cbreak(); 113 114 init_pair(COLOR_BLACK, COLOR_BLACK, -1); 115 init_pair(COLOR_RED, COLOR_RED, -1); 116 init_pair(COLOR_GREEN,COLOR_GREEN, -1); 117 init_pair(COLOR_YELLOW, COLOR_YELLOW, -1); 118 init_pair(COLOR_BLUE, COLOR_BLUE, -1); 119 init_pair(COLOR_MAGENTA, COLOR_MAGENTA, -1); 120 init_pair(COLOR_CYAN, COLOR_CYAN, -1); 121 init_pair(COLOR_WHITE, COLOR_WHITE, -1); 122 123 resize(); 124 } 125 126 void 127 resize(void) 128 { 129 int startx; 130 131 getmaxyx(stdscr, maxy, maxx); 132 133 wmaxy = maxy - BORDER_SPACE_SIZE; 134 wmaxx = maxx/5 - BORDER_SPACE_SIZE > 0 ? maxx/5 - BORDER_SPACE_SIZE : 1; 135 startx = maxx > BORDER_SPACE_SIZE ? BORDER_SPACE_SIZE : 0; 136 wwin = newwin(wmaxy, wmaxx, BORDER_SPACE_SIZE, startx); 137 138 vmaxy = maxy - BORDER_SPACE_SIZE; 139 vmaxx = maxx - wmaxx - BORDER_SPACE_SIZE; 140 startx = wmaxx + BORDER_SPACE_SIZE; 141 vwin = newwin(wmaxy, vmaxx, BORDER_SPACE_SIZE, startx); 142 143 clear(); 144 refresh(); 145 } 146 147 void 148 print_cal(void) 149 { 150 int y = 0; 151 152 wclear(wwin); 153 wclear(vwin); 154 155 attron(COLOR_PAIR(DAY_TITLE_COLOR)); 156 mvprintw(0, 0, "%s %d %s %d\n", 157 DayName[highltm.tm_wday], 158 highltm.tm_mday, 159 MonthName[highltm.tm_mon], 160 highltm.tm_year + 1900); 161 attroff(COLOR_PAIR(DAY_TITLE_COLOR)); 162 163 print_month(-1, &y); 164 print_month(0, &y); 165 print_month(1, &y); 166 167 wrefresh(wwin); 168 } 169 170 void 171 print_month(const int monthadd, int *y) 172 { 173 int wcount, today, wnote, color, x = 0; 174 int notecol; 175 struct tm otm = highltm; 176 177 if (monthadd >= 0) 178 (*y)+=2; 179 otm.tm_mon += monthadd; 180 otm.tm_mday = 1; 181 timegm(&otm); 182 wcount = get_days(&otm); 183 184 wattron(wwin, COLOR_PAIR(MONTH_TITLE_COLOR)); 185 mvwprintw(wwin, *y,0,"%s %d\n", 186 MonthName[otm.tm_mon], 187 otm.tm_year + 1900); 188 wattroff(wwin, COLOR_PAIR(MONTH_TITLE_COLOR)); 189 mvwprintw(wwin, (*y)+=2 ,0,"%s\n", "Su Mo Tu We Th Fr Sa"); 190 191 (*y)+=1; 192 x=otm.tm_wday*3; 193 194 for (int i = 0; i < wcount; ++i) { 195 otm.tm_mday=i+1; 196 today = same_day(&otm, &curtm); 197 notecol = get_relevant_recur(&otm); 198 199 if (today) 200 wattron(wwin, A_UNDERLINE); 201 if ((wnote = (notecol != -1))) { 202 color = get_note_view_col(notecol); 203 wattron(wwin, COLOR_PAIR(color)); 204 } 205 if (monthadd == 0 && highlight == i) { 206 wattron(wwin, A_REVERSE); 207 mvwprintw(wwin, *y, x, "%d", otm.tm_mday); 208 wattroff(wwin, A_REVERSE); 209 } else 210 mvwprintw(wwin, *y, x, "%d", otm.tm_mday); 211 if (today) 212 wattroff(wwin, A_UNDERLINE); 213 if (wnote) 214 wattroff(wwin, COLOR_PAIR(color)); 215 x+=3; 216 if (x % DAY_COLUMN_SIZE == 0 && i != wcount-1) { 217 (*y)++; 218 x = 0; 219 } 220 } 221 } 222 223 void 224 start(void) 225 { 226 int c; 227 228 while ((c = getch()) != KEY_QUIT) { 229 switch (c) { 230 case KEY_UP: 231 case KEY_VUP: 232 move_up(); 233 break; 234 case KEY_DOWN: 235 case KEY_VDOWN: 236 move_down(); 237 break; 238 case 10: 239 print_view(); 240 break; 241 case KEY_RIGHT: 242 case KEY_VRIGHT: 243 case KEY_VRIGHT_ABS: 244 move_right(); 245 break; 246 case KEY_LEFT: 247 case KEY_VLEFT: 248 case KEY_VLEFT_ABS: 249 move_left(); 250 break; 251 case KEY_PPAGE: 252 case ctrl('u'): 253 move_page_up(); 254 break; 255 case KEY_NPAGE: 256 case ctrl('d'): 257 move_page_down(); 258 break; 259 case KEY_EDIT_NOTES: 260 edit_notes(); 261 break; 262 case KEY_COMBO_GO: 263 case KEY_COMBO_MAKE: 264 combo_key(c); 265 break; 266 case KEY_RESIZE: 267 resize(); 268 break; 269 default: 270 break; 271 } 272 print_cal(); 273 print_view(); 274 } 275 } 276 277 void 278 combo_key(int keypress) 279 { 280 int c; 281 282 move(maxy-1, maxx-1); 283 clrtoeol(); 284 addch(keypress); 285 286 halfdelay(10); 287 288 while ((c = getch()) != ERR) { 289 switch (keypress) { 290 case KEY_COMBO_GO: 291 combo_go(c); 292 break; 293 case KEY_COMBO_MAKE: 294 combo_make(c); 295 break; 296 default: 297 break; 298 } 299 break; 300 } 301 302 cbreak(); 303 move(maxy-1, maxx-1); 304 clrtoeol(); 305 } 306 307 void 308 combo_go(int key) 309 { 310 switch (key) { 311 case KEY_COMBO_GO_TODAY: 312 curdate(); 313 break; 314 default: 315 break; 316 } 317 } 318 319 void 320 combo_make(int key) 321 { 322 cbreak(); 323 324 switch (key) { 325 case KEY_COMBO_MAKE_ADD: 326 add_note(&highltm); 327 break; 328 case KEY_COMBO_MAKE_RM: 329 rm_note(); 330 break; 331 default: 332 break; 333 } 334 } 335 336 void 337 add_note(struct tm *tm) { 338 size_t len; 339 char answer[DESCLEN+20], desc[DESCLEN], rec[2], time[6], date[11], prefill[11], span[2]; 340 char end[11] = "2999-12-31"; 341 342 if (tm != NULL) 343 snprintf(prefill, 11, "%04d-%02d-%02d", 344 (tm->tm_year+1900)%10000u, 345 (tm->tm_mon+1)%100u, 346 (tm->tm_mday)%100u); 347 348 if (prompt_answer(rec, arrlen(rec), "Recurring? (0=NO;1=YEARLY;2=MONTHLY;3=WEEKLY;4=DAILY)", "4", "0")) 349 return; 350 if (prompt_answer(date, arrlen(date), "Date?", "2999-19-39", prefill)) 351 return; 352 if (prompt_answer(time, arrlen(time), "Time?", "29:59", NULL)) 353 return; 354 355 if (rec[0] != '0') { 356 if (prompt_answer(span, arrlen(span), "Set end date? (0=NO;1=YES)", "1", "0")) 357 return; 358 if (span[0] == '1') { 359 if (prompt_answer(end, arrlen(end), "End date?", "2999-19-39", NULL)) 360 return; 361 } 362 } 363 364 if (prompt_answer(desc, DESCLEN, "Description?", NULL, NULL)) 365 return; 366 367 snprintf(answer, DESCLEN+30, "%s|%10sT%s|%10s|%s", rec, date, time, end, desc); 368 369 len = strlen(answer); 370 371 if (len > sizeof(rec) + sizeof(date) + sizeof(end) + sizeof(time)) { 372 notes = (Note**) realloc(notes, (sizeof(Note*) * (note_count + 1))); 373 if (notes == NULL) 374 fatal("Fatal: failed to reallocate bytes for notes.\n"); 375 376 notes[note_count] = get_note(answer, len); 377 timegm(¬es[note_count]->time); 378 note_count++; 379 write_notes(); 380 } 381 } 382 383 void 384 edit_notes(void) 385 { 386 if (editor == NULL) 387 fatal("no editor found; set EDITOR environment variable."); 388 389 sigset_t set; 390 char cmd[PATH_MAX + strlen(editor) + 4]; 391 392 endwin(); 393 394 if (sigemptyset (&set) == -1) 395 fatal("Sigemptyset failed."); 396 if (sigaddset(&set, SIGWINCH) == -1) 397 fatal("Sigaddset failed."); 398 if (sigprocmask(SIG_BLOCK, &set, NULL) != 0) 399 fatal("Blocking sigprocmask failed."); 400 401 sprintf(cmd, "%s \"%s\"", editor, file); 402 system(cmd); 403 404 if (sigprocmask(SIG_UNBLOCK, &set, NULL) != 0) 405 fatal("Unblocking sigprocmask failed."); 406 407 clear(); 408 noecho(); 409 cbreak(); 410 411 free_notes(); 412 get_notes(); 413 refresh(); 414 } 415 416 void 417 rm_note(void) { 418 int c, y, x; 419 420 if (curnote_count == 0) 421 return; 422 423 move(BORDER_SPACE_SIZE, wmaxx + BORDER_SPACE_SIZE - 1); 424 curs_set(1); 425 getyx(stdscr, y, x); 426 427 while ((c = wgetch(stdscr)) != 10) { 428 switch (c) { 429 case KEY_BACKSPACE: 430 curs_set(0); 431 return; 432 case KEY_UP: 433 case KEY_VUP: 434 if (y-1 >= BORDER_SPACE_SIZE) 435 move(--y, x); 436 break; 437 case KEY_DOWN: 438 case KEY_VDOWN: 439 if (y+1 < BORDER_SPACE_SIZE + curnote_count) 440 move(++y, x); 441 break; 442 } 443 } 444 int found = 0; 445 for (int i = 0; i < note_count; i++) { 446 if (notes[i] == curnotes[y-BORDER_SPACE_SIZE]) { 447 found = 1; 448 free(notes[i]->description); 449 free(notes[i]); 450 } else if (found) 451 notes[i-1] = notes[i]; 452 } 453 note_count--; 454 write_notes(); 455 curs_set(0); 456 } 457 458 void 459 write_notes(void) 460 { 461 FILE *fp = fopen(file, "w+"); 462 463 if (!fp) 464 fatal("Error opening file '%s'\n", file); 465 466 qsort(notes, note_count, sizeof(Note*), compare_note); 467 468 for (int i = 0; i < note_count; i++) 469 fprintf(fp, "%d|%04d-%02d-%02dT%02d:%02d|%04d-%02d-%02d|%s\n", 470 (int) notes[i]->recurring, 471 notes[i]->time.tm_year+1900, 472 notes[i]->time.tm_mon+1, 473 notes[i]->time.tm_mday, 474 notes[i]->time.tm_hour, 475 notes[i]->time.tm_min, 476 notes[i]->end.tm_year+1900, 477 notes[i]->end.tm_mon+1, 478 notes[i]->end.tm_mday, 479 notes[i]->description); 480 fclose(fp); 481 } 482 483 Note ** 484 get_notes(void) 485 { 486 char *line_buf = NULL; 487 size_t line_buf_size = 0; 488 ssize_t line_size; 489 490 FILE *fp = fopen(file, "r"); 491 492 if (!fp) 493 fatal("Error opening file '%s'\n", file); 494 495 line_size = getline(&line_buf, &line_buf_size, fp); 496 notes = (Note**) malloc(sizeof(Note*)); 497 498 if (notes == NULL) 499 fatal("Fatal: failed to allocate bytes for notes.\n"); 500 501 while (line_size >= 0) { 502 notes = (Note**) realloc(notes, (sizeof(Note*) * (note_count + 1))); 503 if (notes == NULL) 504 fatal("Fatal: failed to reallocate bytes for notes.\n"); 505 506 notes[note_count] = get_note(line_buf, line_size); 507 508 line_size = getline(&line_buf, &line_buf_size, fp); 509 note_count++; 510 } 511 512 free(line_buf); 513 fclose(fp); 514 515 write_notes(); 516 517 return notes; 518 } 519 520 Note * 521 get_note(char *line_buf, ssize_t line_size) 522 { 523 char timestr[19], enddate[11]; 524 Note *note = (Note*) calloc(1, sizeof(Note)); 525 526 if (note == NULL) 527 fatal("Fatal: failed to allocate bytes for note.\n"); 528 529 note->description = (char*) malloc(line_size); 530 531 if (note->description == NULL) 532 fatal("Fatal: failed to allocate bytes for note.\n"); 533 534 sscanf(line_buf, "%d|%[^|]|%[^|]|%[^\n]", 535 (int*) ¬e->recurring, 536 timestr, 537 enddate, 538 note->description); 539 540 note->description[line_size-1] = '\0'; 541 542 if (sscanf(timestr, "%d-%d-%dT%d:%d", 543 &(note->time.tm_year), 544 &(note->time.tm_mon), 545 &(note->time.tm_mday), 546 &(note->time.tm_hour), 547 &(note->time.tm_min)) >= 3) { 548 note->time.tm_year-=1900; 549 note->time.tm_mon-=1; 550 } 551 if (sscanf(enddate, "%d-%d-%d", 552 &(note->end.tm_year), 553 &(note->end.tm_mon), 554 &(note->end.tm_mday)) >= 3) { 555 note->end.tm_year-=1900; 556 note->end.tm_mon-=1; 557 } 558 559 return note; 560 } 561 562 int 563 get_relevant_recur(struct tm *tm) 564 { 565 int relevant = -1; 566 567 for (int i = 0; i < note_count; i++) { 568 if (is_relevant(notes[i], tm)) 569 relevant = MIN(notes[i]->recurring, relevant == -1 ? 4 : relevant); 570 } 571 572 return relevant; 573 } 574 575 void 576 print_view(void) 577 { 578 int y = -1, x = 0, color; 579 580 get_curnotes(); 581 582 for (int i = 0; i < curnote_count; i++) { 583 y++; 584 if ((color = get_note_view_col(curnotes[i]->recurring)) > -1) { 585 wattron(vwin, COLOR_PAIR(color)); 586 mvwaddch(vwin, y, x, '|'); 587 wattroff(vwin, COLOR_PAIR(color)); 588 } 589 mvwprintw(vwin, y, x+1, "%02d:%02d %s\n", 590 curnotes[i]->time.tm_hour, 591 curnotes[i]->time.tm_min, 592 curnotes[i]->description); 593 } 594 wrefresh(vwin); 595 } 596 597 int 598 prompt_answer(char *buf, int size, const char *question, const char *mask, char *prefill) 599 { 600 int c, y, x, nr, phlen = mask == NULL ? 0 : strlen(mask), status = 0, len = 0; 601 char placeholder[phlen]; 602 char *startfill; 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 613 startfill = prefill != NULL ? prefill : placeholder; 614 615 printw(" %s", startfill); 616 617 if (prefill != NULL) { 618 len = strlen(prefill); 619 cpstr(buf, prefill); 620 } 621 622 move(maxy-1, strlen(question)+2 + len); 623 624 echo(); 625 curs_set(1); 626 627 getyx(stdscr, y, x); 628 629 while ((c = wgetch(stdscr)) != 10) { 630 move(maxy-2, 1); 631 clrtoeol(); 632 move(y,x); 633 switch (c) { 634 case KEY_BACKSPACE: 635 if (len > 0) { 636 if (len <= phlen) { 637 --len; 638 buf[len] = placeholder[len]; 639 move(y, --x); 640 attron(COLOR_PAIR(PROMPT_COLOR)); 641 addch(buf[len]); 642 attroff(COLOR_PAIR(PROMPT_COLOR)); 643 move(y, x); 644 if (len > 0 && len <= phlen && placeholder[len] != '_') { 645 --len; 646 buf[len] = placeholder[len]; 647 move(y, --x); 648 attron(COLOR_PAIR(PROMPT_COLOR)); 649 addch(buf[len]); 650 attroff(COLOR_PAIR(PROMPT_COLOR)); 651 move(y, x); 652 } 653 } else { 654 buf[--(len)] = '\0'; 655 move(y, --x); 656 clrtoeol(); 657 } 658 } 659 break; 660 default: 661 if (len < size-1) { 662 if (len < phlen) { 663 if (!isdigit(c) || (nr = mask[len] - '0') < c - '0') { 664 mvprintw(maxy-2, 1, "Only digits (max: %d)", nr); 665 move(y,x); 666 break; 667 } 668 buf[(len)++] = c; 669 move(y, ++x); 670 if (len < phlen && placeholder[len] != '_') { 671 buf[len] = placeholder[len]; 672 len++; 673 move(y, ++x); 674 } 675 } else { 676 buf[(len)++] = c; 677 move(y, ++x); 678 } 679 } else 680 clrtoeol(); 681 break; 682 } 683 } 684 noecho(); 685 curs_set(0); 686 687 buf[phlen > 0 ? phlen : len] = '\0'; 688 689 if (strchr(buf, '_') != NULL || len < phlen) { 690 buf[0] = '\0'; 691 status = 1; 692 } 693 694 move(maxy-1, 1); 695 clrtoeol(); 696 move(maxy-2, 1); 697 clrtoeol(); 698 699 return status; 700 } 701 702 int 703 get_curnotes(void) 704 { 705 if (curnote_count > 0 || curnotes != NULL) { 706 curnote_count = 0; 707 free(curnotes); 708 curnotes = NULL; 709 } 710 711 curnotes = (Note**) malloc(sizeof(Note*)); 712 713 if (curnotes == NULL) 714 fatal("Fatal: failed to allocate bytes for notes.\n"); 715 716 for (int i = 0; i < note_count; i++) { 717 if (is_relevant(notes[i], &highltm)) { 718 curnotes = (Note**) realloc(curnotes, (sizeof(Note*) * (curnote_count + 1))); 719 if (curnotes == NULL) 720 fatal("Fatal: failed to reallocate bytes for notes.\n"); 721 curnotes[curnote_count] = notes[i]; 722 curnote_count++; 723 } 724 } 725 return 0; 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 }