scal

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

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