scal

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

scal.c (18405B)


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