scal

Simple Calendar
git clone git://git.wimdupont.com/scal.git
Log | Files | Refs | README | LICENSE

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(&notes[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*) &note->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(&note->time);
    772 	timegm(&note->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 }