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