viscal

cairo/gtk vi-like timeblocking calendar
git clone git://jb55.com/viscal
Log | Files | Refs | README | LICENSE

viscal.c (57503B)


      1 
      2 #include <cairo/cairo.h>
      3 #include <gtk/gtk.h>
      4 #include <gdk/gdkkeysyms.h>
      5 #include <libical/ical.h>
      6 #include <assert.h>
      7 #include <time.h>
      8 #include <string.h>
      9 #include <stdlib.h>
     10 #include <math.h>
     11 #include <locale.h>
     12 #include <stdbool.h>
     13 
     14 #define ARRAY_SIZE(array) (sizeof((array))/sizeof((array)[0]))
     15 
     16 #define sgn(x) (((x) > 0) - ((x) < 0))
     17 
     18 #define clamp(val, low, high) (val < low ? low : (val > high ? high : val))
     19 
     20 #define max(a,b)                                \
     21   ({ __typeof__ (a) _a = (a);                   \
     22     __typeof__ (b) _b = (b);                    \
     23     _a > _b ? _a : _b; })
     24 
     25 #define min(a,b)                                \
     26   ({ __typeof__ (a) _a = (a);                   \
     27     __typeof__ (b) _b = (b);                    \
     28     _a < _b ? _a : _b; })
     29 
     30 #define EDITBUF_MAX 32768
     31 
     32 // TODO: heap-realloc events array
     33 #define MAX_EVENTS 1024
     34 #define SMALLEST_TIMEBLOCK 5
     35 
     36 static icaltimezone *tz_utc;
     37 static const double BGCOLOR = 0.35;
     38 static const int DAY_SECONDS = 86400;
     39 static const int TXTPAD = 11;
     40 static const int EVPAD = 2;
     41 static const int EVMARGIN = 1;
     42 static const double ZOOM_MAX = 10;
     43 static const double ZOOM_MIN = 1;
     44 /* static const int DEF_LMARGIN = 20; */
     45 
     46 
     47 enum event_flags {
     48     EV_SELECTED    = 1 << 0
     49   , EV_HIGHLIGHTED = 1 << 1
     50   , EV_DRAGGING    = 1 << 2
     51   , EV_IMMOVABLE   = 1 << 3
     52 };
     53 
     54 enum cal_flags {
     55     CAL_MDOWN      = 1 << 0
     56   , CAL_DRAGGING   = 1 << 1
     57   , CAL_SPLIT      = 1 << 2
     58   , CAL_CHANGING   = 1 << 3
     59   , CAL_INSERTING  = 1 << 4
     60 };
     61 
     62 union rgba {
     63   double rgba[4];
     64   struct {
     65     double r, g, b, a;
     66   };
     67 };
     68 
     69 enum source {
     70 	SOURCE_CALDAV,
     71 	SOURCE_FILE
     72 };
     73 
     74 struct ical {
     75 	icalcomponent * calendar;
     76 	enum source source;
     77 	const char *source_location;
     78 	union rgba color;
     79 	bool visible;
     80 };
     81 
     82 struct event {
     83 	icalcomponent *vevent;
     84 	struct ical *ical;
     85 
     86 	int flags;
     87 	// set on draw
     88 	double width, height;
     89 	double x, y;
     90 	double dragx, dragy;
     91 	double dragx_off, dragy_off;
     92 	time_t drag_time;
     93 };
     94 
     95 // used for temporary storage when editing summaries, descriptions, etc
     96 static char g_editbuf[EDITBUF_MAX] = {0};
     97 static int g_editbuf_pos = 0;
     98 
     99 // TODO: move or remove g_cal_tz
    100 static icaltimezone *g_cal_tz;
    101 
    102 struct cal {
    103 	GtkWidget *widget;
    104 	struct ical calendars[128];
    105 	int ncalendars;
    106 
    107 	struct event events[MAX_EVENTS];
    108 	int nevents;
    109 	char chord;
    110 	int repeat;
    111 
    112 	icalcomponent *select_after_sort;
    113 	// TODO: make multiple target selection
    114 	int target;
    115 	int selected_event_ind;
    116 	int selected_calendar_ind;
    117 
    118 	enum cal_flags flags;
    119 	int timeblock_size;
    120 	int timeblock_step;
    121 	int refresh_events;
    122 	int x, y, mx, my;
    123 	int gutter_height;
    124 	int font_size;
    125 	double zoom, zoom_at;
    126 	icaltimezone *tz;
    127 
    128 	time_t current; // current highlighted position
    129 	time_t today, start_at, scroll;
    130 
    131 	int height, width;
    132 };
    133 
    134 typedef void (chord_cmd)(struct cal *);
    135 
    136 struct chord {
    137 	char *keys;
    138 	chord_cmd *cmd;
    139 };
    140 
    141 static void align_hour(struct cal *);
    142 static void align_up(struct cal *);
    143 static void align_down(struct cal *);
    144 static void center_view(struct cal *);
    145 static void top_view(struct cal *);
    146 static void bottom_view(struct cal *);
    147 static void zoom_in(struct cal *);
    148 static void zoom_out(struct cal *);
    149 static void select_down(struct cal *);
    150 static void select_up(struct cal *);
    151 static void delete_timeblock(struct cal *);
    152 
    153 static struct chord chords[] = {
    154 	{ "ah", align_hour },
    155 	{ "ak", align_up },
    156 	{ "aj", align_down },
    157 	{ "zz", center_view },
    158 	{ "zt", top_view },
    159 	{ "zb", bottom_view },
    160 	{ "zi", zoom_in },
    161 	{ "zo", zoom_out },
    162 	{ "gj", select_down },
    163 	{ "gk", select_up },
    164 	{ "dd", delete_timeblock },
    165 };
    166 
    167 struct extra_data {
    168   GtkWindow *win;
    169   struct cal *cal;
    170 };
    171 
    172 static GdkCursor *cursor_default;
    173 static GdkCursor *cursor_pointer;
    174 
    175 static int g_lmargin = 18;
    176 static int g_margin_time_w = 0;
    177 static int margin_calculated = 0;
    178 
    179 static union rgba g_text_color;
    180 /* static union rgba g_timeline_color; */
    181 
    182 static const double dashed[] = {1.0};
    183 
    184 static void
    185 calendar_create(struct cal *cal) {
    186 	time_t now;
    187 	time_t today, nowh;
    188 	struct tm nowtm;
    189 
    190 	now = time(NULL);
    191 	nowtm = *localtime(&now);
    192 	nowtm.tm_min = 0;
    193 	nowh = mktime(&nowtm);
    194 	nowtm.tm_hour = 0;
    195 	nowtm.tm_sec = 0;
    196 	today = mktime(&nowtm);
    197 
    198 	cal->selected_calendar_ind = 0;
    199 	cal->selected_event_ind = -1;
    200 	cal->select_after_sort = NULL;
    201 	cal->target = -1;
    202 	cal->chord = 0;
    203 	cal->gutter_height = 40;
    204 	cal->font_size = 16;
    205 	cal->timeblock_step = 15;
    206 	cal->timeblock_size = 30;
    207 	cal->flags = 0;
    208 	cal->ncalendars = 0;
    209 	cal->nevents = 0;
    210 	cal->start_at = nowh - today - 4*60*60;
    211 	cal->scroll = 0;
    212 	cal->current = nowh;
    213 	cal->repeat = 1;
    214 	cal->today = today;
    215 	cal->x = g_lmargin;
    216 	cal->y = cal->gutter_height;
    217 	cal->zoom = 5.0;
    218 }
    219 
    220 static void warn(const char *msg) {
    221 	printf("WARN %s\n", msg);
    222 }
    223 
    224 
    225 static void set_current_calendar(struct cal *cal, struct ical *ical)
    226 {
    227 	for (int i = 0; i < cal->ncalendars; i++) {
    228 		if (&cal->calendars[i] == ical)
    229 			cal->selected_calendar_ind = i;
    230 	}
    231 }
    232 
    233 static struct ical *current_calendar(struct cal *cal) {
    234 	if (cal->ncalendars == 0)
    235 		return NULL;
    236 
    237 	return &cal->calendars[cal->selected_calendar_ind];
    238 }
    239 
    240 static time_t calendar_view_end(struct cal *cal)
    241 {
    242 	return cal->today + cal->start_at + cal->scroll + DAY_SECONDS;
    243 }
    244 
    245 
    246 static time_t calendar_view_start(struct cal *cal)
    247 {
    248 	return cal->today + cal->start_at + cal->scroll;
    249 }
    250 
    251 
    252 
    253 static int
    254 span_overlaps(time_t start1, time_t end1, time_t start2, time_t end2) {
    255 	return max(0, min(end1, end2) - max(start1, start2));
    256 }
    257 
    258 
    259 static void vevent_span_timet(icalcomponent *vevent, time_t *st, time_t *et)
    260 {
    261 	icaltimetype dtstart, dtend;
    262 
    263 	if (st) {
    264 		dtstart = icalcomponent_get_dtstart(vevent);
    265 		*st = icaltime_as_timet_with_zone(dtstart, g_cal_tz);
    266 	}
    267 
    268 	if (et) {
    269 		dtend = icalcomponent_get_dtend(vevent);
    270 		*et = icaltime_as_timet_with_zone(dtend, g_cal_tz);
    271 	}
    272 }
    273 
    274 static void select_event(struct cal *cal, int ind)
    275 {
    276 	time_t start ,end;
    277 	struct event *ev;
    278 
    279 	cal->selected_event_ind = ind;
    280 
    281 	if (ind != -1) {
    282 		ev = &cal->events[ind];
    283 		vevent_span_timet(ev->vevent, &start, NULL);
    284 		cal->current = start;
    285 	}
    286 }
    287 
    288 /* static int */
    289 /* vevent_in_span(icalcomponent *vevent, time_t start, time_t end) { */
    290 /* 	/\* printf("vevent_in_span span.start %d span.end %d start %d end %d\n", *\/ */
    291 /* 	/\* 	span.start, span.end, start, end); *\/ */
    292 /* 	time_t st, et; */
    293 /* 	vevent_span_timet(vevent, &st, &et); */
    294 /* 	return span_overlaps(st, et, start, end); */
    295 
    296 /* } */
    297 
    298 static int sort_event(const void *a, const void*b) {
    299 	time_t st_a, st_b;
    300 	icaltimetype dtstart;
    301 	struct event *ea = (struct event *)a;
    302 	struct event *eb = (struct event *)b;
    303 
    304 	dtstart = icalcomponent_get_dtstart(ea->vevent);
    305 	st_a = icaltime_as_timet_with_zone(dtstart, dtstart.zone);
    306 
    307 	dtstart = icalcomponent_get_dtstart(eb->vevent);
    308 	st_b = icaltime_as_timet_with_zone(dtstart, dtstart.zone);
    309 
    310 	if (st_a < st_b)
    311 		return -1;
    312 	else if (st_a == st_b)
    313 		return 0;
    314 	else
    315 		return 1;
    316 }
    317 
    318 static time_t get_vevent_start(icalcomponent *vevent)
    319 {
    320 	icaltimetype dtstart = icalcomponent_get_dtstart(vevent);
    321 	return icaltime_as_timet_with_zone(dtstart, dtstart.zone);
    322 }
    323 
    324 static int first_event_starting_at(struct cal *cal, time_t starting_at)
    325 {
    326 	time_t st;
    327 
    328 	if (cal->nevents == 0)
    329 		return -1;
    330 
    331 	for (int i = cal->nevents - 1; i >= 0; i--) {
    332 		vevent_span_timet(cal->events[i].vevent, &st, NULL);
    333 
    334 		if (st >= starting_at)
    335 			continue;
    336 		else if (i == cal->nevents - 1)
    337 			return -1;
    338 
    339 		return i + 1;
    340 	}
    341 
    342 	assert(!"unpossible");
    343 }
    344 
    345 // seconds_range = 0 implies: do something reasonable (DAY_SECONDS/4)
    346 static int find_event_within(struct cal *cal, time_t target, int seconds_range)
    347 {
    348 	struct event *ev;
    349 	time_t evtime, diff, prev;
    350 
    351 	if (seconds_range == 0)
    352 		seconds_range = DAY_SECONDS/4;
    353 
    354 	prev = target;
    355 
    356 	if (cal->nevents == 0)
    357 		return -1;
    358 	else if (cal->nevents == 1)
    359 		return 0;
    360 
    361 	for (int i = cal->nevents-1; i >= 0; i--) {
    362 
    363 		ev = &cal->events[i];
    364 		vevent_span_timet(ev->vevent, &evtime, NULL);
    365 
    366 		diff = abs(target - evtime);
    367 
    368 		if (diff > prev) {
    369 			if (prev > seconds_range)
    370 				return -1;
    371 			return i+1;
    372 		}
    373 
    374 		prev = diff;
    375 	}
    376 
    377 	assert(!"shouldn't get here");
    378 }
    379 
    380 /* static void select_closest_to_now(struct cal *cal) */
    381 /* { */
    382 /* 	time_t now = time(NULL); */
    383 /* 	cal->selected_event_ind = find_event_within(cal, now, 0); */
    384 /* } */
    385 
    386 
    387 static void set_edit_buffer(const char *src)
    388 {
    389 	char *dst = g_editbuf;
    390 	int n = 0;
    391 	int c = EDITBUF_MAX;
    392 
    393 	while (c-- && (*dst++ = *src++))
    394 		n++;
    395 
    396 	g_editbuf_pos = n;
    397 }
    398 
    399 static struct ical *get_selected_calendar(struct cal *cal)
    400 {
    401 	if (cal->ncalendars == 0 || cal->selected_calendar_ind == -1)
    402 		return NULL;
    403 	return &cal->calendars[cal->selected_calendar_ind];
    404 }
    405 
    406 static struct event *get_selected_event(struct cal *cal)
    407 {
    408 	if (cal->nevents == 0 || cal->selected_event_ind == -1)
    409 		return NULL;
    410 	return &cal->events[cal->selected_event_ind];
    411 }
    412 
    413 enum edit_mode_flags {
    414 	EDIT_CLEAR = 1 << 1,
    415 };
    416 
    417 static void edit_mode(struct cal *cal, int flags)
    418 {
    419 	// TODO: STATUS BAR for edit mode
    420 
    421 	struct event *event =
    422 		get_selected_event(cal);
    423 
    424 	// don't enter edit mode if we're not selecting any event
    425 	if (!event)
    426 		return;
    427 
    428 	cal->flags |= CAL_CHANGING;
    429 
    430 	if (flags & EDIT_CLEAR)
    431 		return set_edit_buffer("");
    432 
    433 	const char *summary =
    434 		icalcomponent_get_summary(event->vevent);
    435 
    436 	// TODO: what are we editing? for now assume summary
    437 	// copy current summary to edit buffer
    438 	set_edit_buffer(summary);
    439 }
    440 
    441 
    442 static void print_flags(struct cal *cal)
    443 {
    444 	int i;
    445 
    446 	printf("flags: ");
    447 	for (i = 0; i < cal->nevents; i++) {
    448 		printf("%d ", cal->events[i].flags);
    449 	}
    450 	printf("\n");
    451 }
    452 
    453 static void events_for_view(struct cal *cal, time_t start, time_t end)
    454 {
    455 	int i;
    456 	struct event *event;
    457 	icalcomponent *vevent;
    458 	struct ical *calendar;
    459 	icalcomponent *ical;
    460 
    461 	cal->nevents = 0;
    462 
    463 	for (i = 0; i < cal->ncalendars; ++i) {
    464 		calendar = &cal->calendars[i];
    465 		ical = calendar->calendar;
    466 		for (vevent = icalcomponent_get_first_component(ical, ICAL_VEVENT_COMPONENT);
    467 		     vevent != NULL && cal->nevents < MAX_EVENTS;
    468 		     vevent = icalcomponent_get_next_component(ical, ICAL_VEVENT_COMPONENT))
    469 		{
    470 
    471 			// NOTE: re-add me when we care about filtering
    472 			/* if (vevent_in_span(vevent, start, end)) { */
    473 			event = &cal->events[cal->nevents++];
    474 			/* printf("event in view %s\n", icalcomponent_get_summary(vevent)); */
    475 			event->vevent = vevent;
    476 			event->ical = calendar;
    477 			/* } */
    478 		}
    479 	}
    480 
    481 	print_flags(cal);
    482 
    483 	printf("DEBUG sorting\n");
    484 	qsort(cal->events, cal->nevents, sizeof(struct event), sort_event);
    485 
    486 	print_flags(cal);
    487 
    488 	// useful for selecting a new event after insertion
    489 	if (cal->select_after_sort) {
    490 		for (i = 0; i < cal->nevents; i++) {
    491 			if (cal->events[i].vevent == cal->select_after_sort) {
    492 				select_event(cal, i);
    493 				// HACK: we might not always want to do this...
    494 				edit_mode(cal, 0);
    495 				break;
    496 			}
    497 		}
    498 	}
    499 
    500 	cal->select_after_sort = NULL;
    501 }
    502 
    503 
    504 static void on_change_view(struct cal *cal) {
    505 	events_for_view(cal, calendar_view_start(cal), calendar_view_end(cal));
    506 }
    507 
    508 
    509 /* static void */
    510 /* calendar_print_state(struct cal *cal) { */
    511 /* 	static int c = 0; */
    512 /* 	printf("%f %d %d %s %s %d\r", */
    513 /* 	       cal->zoom, cal->mx, cal->my, */
    514 /* 	       (cal->flags & CAL_DRAGGING) != 0 ? "D " : "  ", */
    515 /* 	       (cal->flags & CAL_MDOWN)    != 0 ? "M " : "  ", */
    516 /* 	       c++ */
    517 /* 		); */
    518 /* 	fflush(stdout); */
    519 /* } */
    520 
    521 static void calendar_refresh_events(struct cal *cal) {
    522 	cal->refresh_events = 1;
    523 	gtk_widget_queue_draw(cal->widget);
    524 }
    525 
    526 
    527 static int on_state_change(GtkWidget *widget, GdkEvent *ev, gpointer user_data) {
    528 	struct extra_data *data = (struct extra_data*)user_data;
    529 	struct cal *cal = data->cal;
    530 
    531 		/* calendar_refresh_events(cal); */
    532 	gtk_widget_queue_draw(cal->widget);
    533 	/* calendar_print_state(cal); */
    534 
    535 	return 1;
    536 }
    537 
    538 
    539 static char * file_load(char *path) {
    540 	FILE *f = fopen(path, "rb");
    541 	if (!f) return NULL;
    542 	fseek(f, 0, SEEK_END);
    543 	long fsize = ftell(f);
    544 	fseek(f, 0, SEEK_SET);
    545 
    546 	char *string = malloc(fsize);
    547 	int res = fread(string, fsize, 1, f);
    548 	if (!res) return NULL;
    549 	fclose(f);
    550 	return string;
    551 }
    552 
    553 static struct ical * calendar_load_ical(struct cal *cal, char *path) {
    554 	// TODO: don't load duplicate calendars
    555 	struct ical* ical;
    556 
    557 	// TODO: free icalcomponent somewhere
    558 	const char *str = file_load(path);
    559 	if (str == NULL)
    560 		return NULL;
    561 
    562 	icalcomponent *calendar = icalparser_parse_string(str);
    563 	if (!calendar)
    564 		return NULL;
    565 
    566 	// TODO: support >128 calendars
    567 	if (ARRAY_SIZE(cal->calendars) == cal->ncalendars)
    568 		return NULL;
    569 
    570 	ical = &cal->calendars[cal->ncalendars++];
    571 	ical->calendar = calendar;
    572 	ical->visible = true;
    573 
    574 	free((void*)str);
    575 	return ical;
    576 }
    577 
    578 
    579 /* static void */
    580 /* event_set_start(struct event *ev, time_t time, const icaltimezone *zone) { */
    581 /*   if (zone == NULL) */
    582 /*     zone = g_timezone; */
    583 /*   icaltimetype ictime = icaltime_from_timet_with_zone_with_zone(time, 1, zone); */
    584 /*   icalcomponent_set_dtstart(ev->vevent, ictime); */
    585 /* } */
    586 
    587 /* static void */
    588 /* event_set_end(struct event *ev, time_t time, const icaltimezone *zone) { */
    589 /*   if (zone == NULL) */
    590 /*     zone = g_timezone; */
    591 /*   icaltimetype ictime = icaltime_from_timet_with_zone_with_zone(time, 1, zone); */
    592 /*   icalcomponent_set_dtend(ev->vevent, ictime); */
    593 /* } */
    594 
    595 
    596 static struct event *get_target(struct cal *cal) {
    597 	if (cal->target == -1)
    598 		return NULL;
    599 
    600 	return &cal->events[cal->target];
    601 }
    602 
    603 static icaltimetype icaltime_from_timet_ours(time_t time, int is_date,
    604 					     struct cal *cal)
    605 {
    606 	icaltimezone *tz;
    607 	tz = cal == NULL ? g_cal_tz : cal->tz;
    608 
    609 	return icaltime_from_timet_with_zone(time, is_date, tz);
    610 }
    611 
    612 static void calendar_drop(struct cal *cal, double mx, double my) {
    613 	struct event *ev = get_target(cal);
    614 
    615 	if (!ev)
    616 		return;
    617 
    618 	icaltime_span span = icalcomponent_get_span(ev->vevent);
    619 
    620 	// TODO: use default event ARRAY_SIZE when dragging from gutter?
    621 	time_t len = span.end - span.start;
    622 
    623 	// XXX: should dragging timezone be the local timezone?
    624 	// XXX: this will probably destroy the timezone, we don't want that
    625 	// TODO: convert timezone on drag?
    626 
    627 	icaltimetype startt =
    628 		icaltime_from_timet_ours(ev->drag_time, 0, cal);
    629 
    630 	icalcomponent_set_dtstart(ev->vevent, startt);
    631 
    632 	icaltimetype endt =
    633 		icaltime_from_timet_ours(ev->drag_time + len, 0, cal);
    634 
    635 	icalcomponent_set_dtend(ev->vevent, endt);
    636 }
    637 
    638 
    639 static time_t location_to_time(time_t start, time_t end, double loc) {
    640 	return (time_t)((double)start) + (loc * (end - start));
    641 }
    642 
    643 
    644 
    645 static time_t calendar_pos_to_time(struct cal *cal, double y) {
    646 	// TODO: this is wrong wrt. zoom
    647 	return location_to_time(calendar_view_start(cal),
    648 				calendar_view_end(cal),
    649 				y/((double)cal->height * cal->zoom));
    650 }
    651 
    652 static time_t calendar_loc_to_time(struct cal *cal, double y) {
    653 	return location_to_time(calendar_view_start(cal),
    654 				calendar_view_end(cal),
    655 				y/cal->zoom);
    656 }
    657 
    658 static void event_click(struct cal *cal, struct event *event, int mx, int my) {
    659 	printf("clicked %s\n", icalcomponent_get_summary(event->vevent));
    660 
    661 	calendar_pos_to_time(cal, my);
    662 }
    663 
    664 // TODO: this should handle zh_CN and others as well
    665 void time_remove_seconds(char *time, int n) {
    666 	int len = strlen(time);
    667 	int count = 0;
    668 	char *ws;
    669 	for (int i = 0; i < len; ++i) {
    670 		if (count == n) {
    671 			ws = &time[i];
    672 			while (*ws != '\0' &&
    673 			       (*ws == ':' ||
    674 				(*ws >= '0' && *ws <= '9'))) ws++;
    675 			len = strlen(ws);
    676 			memcpy(&time[i-1], ws, len);
    677 			time[i-1+len] = '\0';
    678 			return;
    679 		}
    680 		// FIXME: instead of (==':'), we want (!= 0..9), in a unicode-enumerated way
    681 		count += time[i] == ':' ? 1 : 0;
    682 	}
    683 }
    684 
    685 
    686 static char *format_locale_time(char *buffer, int bsize, struct tm *tm) {
    687 	strftime(buffer, bsize, "%X", tm);
    688 	time_remove_seconds(buffer, 2);
    689 	return buffer;
    690 }
    691 
    692 
    693 static char *format_locale_timet(char *buffer, int bsize, time_t time) {
    694 	struct tm lt;
    695 	lt = *localtime(&time);
    696 	return format_locale_time(buffer, bsize, &lt);
    697 }
    698 
    699 
    700 
    701 static icalcomponent *create_event(struct cal *cal, time_t start, time_t end,
    702 				   icalcomponent *ical) {
    703 	static const char *default_event_summary = "";
    704 	icalcomponent *vevent;
    705 	icaltimetype dtstart = icaltime_from_timet_ours(start, 0, cal);
    706 	icaltimetype dtend = icaltime_from_timet_ours(end, 0, cal);
    707 
    708 	vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT);
    709 
    710 	icalcomponent_set_summary(vevent, default_event_summary);
    711 	icalcomponent_set_dtstart(vevent, dtstart);
    712 	icalcomponent_set_dtend(vevent, dtend);
    713 	icalcomponent_add_component(ical, vevent);
    714 
    715 	calendar_refresh_events(cal);
    716 	cal->select_after_sort = vevent;
    717 
    718 	return vevent;
    719 }
    720 
    721 static icalcomponent *calendar_def_cal(struct cal *cal) {
    722   // TODO: configurable default calendar
    723   if (cal->ncalendars > 0)
    724     return cal->calendars[0].calendar;
    725   return NULL;
    726 }
    727 
    728 static time_t closest_timeblock_for_timet(time_t st, int timeblock_size) {
    729 	struct tm lt;
    730 	lt = *localtime(&st);
    731 	lt.tm_min = round(lt.tm_min / timeblock_size) * timeblock_size;
    732 	lt.tm_sec = 0; // removes jitter
    733 	return mktime(&lt);
    734 }
    735 
    736 static time_t closest_timeblock(struct cal *cal, int y) {
    737 	time_t st = calendar_pos_to_time(cal, y);
    738 	return closest_timeblock_for_timet(st, cal->timeblock_size);
    739 }
    740 
    741 
    742 
    743 static int event_hit (struct event *ev, double mx, double my) {
    744 	return
    745 		mx >= ev->x
    746 		&& mx <= (ev->x + ev->width)
    747 		&& my >= ev->y
    748 		&& my <= (ev->y + ev->height);
    749 }
    750 
    751 
    752 static int events_hit (struct event *events, int nevents,
    753 				 double mx, double my)
    754 {
    755 	for (int i = 0; i < nevents; ++i) {
    756 		if (event_hit(&events[i], mx, my))
    757 			return i;
    758 	}
    759 	return -1;
    760 }
    761 
    762 static void zoom(struct cal *cal, double amt)
    763 {
    764 	double newzoom = cal->zoom - amt * max(0.1, log(cal->zoom)) * 0.5;
    765 
    766 	if (newzoom < ZOOM_MIN) {
    767 		newzoom = ZOOM_MIN;
    768 	}
    769 	else if (newzoom > ZOOM_MAX) {
    770 		newzoom = ZOOM_MAX;
    771 	}
    772 
    773 	cal->zoom = newzoom;
    774 	cal->zoom_at = cal->my;
    775 }
    776 
    777 static int event_minutes(struct event *event)
    778 {
    779 	time_t st, et;
    780 	vevent_span_timet(event->vevent, &st, &et);
    781 	return (et - st) / 60;
    782 }
    783 
    784 
    785 static int timeblock_size(struct cal *cal)
    786 {
    787 	if (cal->selected_event_ind != -1) {
    788 		struct event *ev = get_selected_event(cal);
    789 		return event_minutes(ev);
    790 	}
    791 
    792 	return cal->timeblock_size;
    793 }
    794 
    795 static int find_closest_event(struct cal *cal, time_t near, int rel)
    796 {
    797 	struct event *ev;
    798 	int is_up, ind;
    799 	time_t start, end, diff, prev;
    800 
    801 	is_up = rel == -1;
    802 	prev = near;
    803 
    804 	if (cal->nevents == 0)
    805 		return -1;
    806 	else if (cal->nevents == 1)
    807 		return 0;
    808 
    809 	for (int i = cal->nevents-1; i >= 0; i--) {
    810 		ev = &cal->events[i];
    811 		vevent_span_timet(ev->vevent, &start, &end);
    812 
    813 		if (end <= near) {
    814 			ind = is_up ? i : i+1;
    815 			return ind == cal->nevents ? -1 : ind;
    816 		}
    817 	}
    818 
    819 	return 0;
    820 }
    821 
    822 static inline int relative_selection(struct cal *cal, int rel)
    823 {
    824 	if (cal->selected_event_ind == -1) {
    825 		return find_closest_event(cal, cal->current, rel);
    826 	}
    827 
    828 	return clamp(cal->selected_event_ind + rel, 0, cal->nevents - 1);
    829 }
    830 
    831 static time_t get_hour(time_t current)
    832 {
    833 	struct tm current_tm;
    834 	current_tm = *localtime(&current);
    835 	current_tm.tm_min = 0;
    836 	current_tm.tm_sec = 0;
    837 	return mktime(&current_tm);
    838 }
    839 
    840 static int get_minute(time_t current)
    841 {
    842 	struct tm current_tm;
    843 	current_tm = *localtime(&current);
    844 	return current_tm.tm_min;
    845 }
    846 
    847 static time_t get_smallest_closest_timeblock(time_t current, int round_by)
    848 {
    849 	struct tm current_tm;
    850 	current_tm = *localtime(&current);
    851 	current_tm.tm_min =
    852 		round(current_tm.tm_min / (double)round_by) * round_by;
    853 	current_tm.tm_sec = 0;
    854 
    855 	return mktime(&current_tm);
    856 }
    857 
    858 static void align_down(struct cal *cal)
    859 {
    860 	/* assert(!"implement me"); */
    861 	struct event *event =
    862 		get_selected_event(cal);
    863 	(void)event;
    864 }
    865 
    866 static void align_up(struct cal *cal)
    867 {
    868 	/* assert(!"implement me"); */
    869 	struct event *event =
    870 		get_selected_event(cal);
    871 	(void)event;
    872 }
    873 
    874 static void align_hour(struct cal *cal)
    875 {
    876 	struct tm current_tm;
    877 	time_t hour;
    878 	current_tm = *localtime(&cal->current);
    879 	current_tm.tm_min =
    880 		round(current_tm.tm_min / 60.0) * 60;
    881 	current_tm.tm_sec = 0;
    882 	hour = mktime(&current_tm);
    883 
    884 	printf("tm_min %d\n", current_tm.tm_min);
    885 
    886 	cal->current = hour;
    887 }
    888 
    889 static void move_event_to(struct cal *cal, struct event *event, time_t to)
    890 {
    891 	time_t st, et;
    892 
    893 	vevent_span_timet(event->vevent, &st, &et);
    894 
    895 	icaltimetype dtstart =
    896 		icaltime_from_timet_ours(to, 0, NULL);
    897 
    898 	icaltimetype dtend =
    899 		icaltime_from_timet_ours(to + (et - st), 0, NULL);
    900 
    901 	icalcomponent_set_dtstart(event->vevent, dtstart);
    902 	icalcomponent_set_dtend(event->vevent, dtend);
    903 
    904 	calendar_refresh_events(cal);
    905 }
    906 
    907 static void move_event_now(struct cal *cal)
    908 {
    909 	struct event *event =
    910 		get_selected_event(cal);
    911 
    912 	if (event == NULL)
    913 		return;
    914 
    915 	time_t closest =
    916 		get_smallest_closest_timeblock(time(NULL), SMALLEST_TIMEBLOCK);
    917 
    918 	move_event_to(cal, event, closest);
    919 }
    920 
    921 static int time_in_view(struct cal *cal, time_t time) {
    922 	time_t st = calendar_loc_to_time(cal, 0);
    923 	time_t et = calendar_loc_to_time(cal, 1.0);
    924 
    925 	return time >= st && time <= et;
    926 }
    927 
    928 static int timeline_in_view(struct cal *cal)
    929 {
    930 	return time_in_view(cal, cal->current);
    931 }
    932 
    933 static void deselect(struct cal *cal)
    934 {
    935 	cal->selected_event_ind = -1;
    936 }
    937 
    938 static void move_now(struct cal *cal)
    939 {
    940 	deselect(cal);
    941 
    942 	cal->current =
    943 		get_smallest_closest_timeblock(time(NULL), SMALLEST_TIMEBLOCK);
    944 
    945 	if (!timeline_in_view(cal))
    946 		center_view(cal);
    947 }
    948 
    949 static void insert_event(struct cal *cal, time_t st, time_t et,
    950 			 struct ical *ical)
    951 {
    952 	cal->flags |= CAL_INSERTING;
    953 	create_event(cal, st, et, ical->calendar);
    954 }
    955 
    956 static void insert_event_action_with(struct cal *cal, time_t st)
    957 {
    958 	// we should eventually always have a calendar
    959 	// at least a temporary one
    960 	if (cal->ncalendars == 0)
    961 		return;
    962 
    963 	time_t et = st + cal->timeblock_size * 60;
    964 
    965 	insert_event(cal, st, et, current_calendar(cal));
    966 }
    967 
    968 static void insert_event_after_action(struct cal *cal)
    969 {
    970 	time_t start = cal->current + cal->timeblock_size * 60;
    971 	insert_event_action_with(cal, start);
    972 }
    973 
    974 
    975 static void insert_event_action(struct cal *cal)
    976 {
    977 	insert_event_action_with(cal, cal->current);
    978 }
    979 
    980 
    981 static void calendar_view_clicked(struct cal *cal, int mx, int my) {
    982 	time_t closest;
    983 	/* int y; */
    984 	char buf[32];
    985 
    986 	closest = closest_timeblock(cal, my);
    987 
    988 	format_locale_timet(buf, sizeof(buf), closest);
    989 	printf("DEBUG (%d,%d) clicked @%s\n", mx, my, buf);
    990 	insert_event_action(cal);
    991 }
    992 
    993 
    994 static int query_span(struct cal *cal, int index_hint, time_t start, time_t end,
    995 		      time_t min_start, time_t max_end)
    996 {
    997 	time_t st, et;
    998 	struct event *ev;
    999 
   1000 	for (int i=index_hint; i < cal->nevents; i++) {
   1001 		ev = &cal->events[i];
   1002 
   1003 		if (!ev->ical->visible)
   1004 			continue;
   1005 
   1006 		icaltimetype dtstart =
   1007 			icalcomponent_get_dtstart(ev->vevent);
   1008 
   1009 		// date events aren't spans
   1010 		if (dtstart.is_date)
   1011 			continue;
   1012 
   1013 		vevent_span_timet(ev->vevent, &st, &et);
   1014 
   1015 		if ((min_start != 0 && st < min_start) ||
   1016 		    (max_end   != 0 && et > max_end))
   1017 			continue;
   1018 		else if (span_overlaps(st, et, start, end))
   1019 			return i;
   1020 	}
   1021 
   1022 	return -1;
   1023 }
   1024 
   1025 // TODO comebine parts with move_event?
   1026 static void move_relative(struct cal *cal, int rel)
   1027 {
   1028 	time_t st;
   1029 	time_t et;
   1030 	int hit;
   1031 	int timeblock = cal->timeblock_size * 60;
   1032 
   1033 	// no current event selection
   1034 	if (cal->selected_event_ind == -1)  {
   1035 		cal->current += rel * timeblock;
   1036 	}
   1037 	else { // and event is selection
   1038 		struct event *ev = get_selected_event(cal);
   1039 		vevent_span_timet(ev->vevent, &st, &et);
   1040 
   1041 		cal->current = rel > 0 ? et : st - timeblock;
   1042 	}
   1043 
   1044 	st = cal->current;
   1045 	et = cal->current + timeblock;
   1046 
   1047 	if ((hit = query_span(cal, 0, st, et, 0, 0)) != -1) {
   1048 		struct event *ev = &cal->events[hit];
   1049 		vevent_span_timet(ev->vevent, &st, &et);
   1050 
   1051 		cal->current = st;
   1052 	}
   1053 
   1054 	cal->selected_event_ind = hit;
   1055 }
   1056 
   1057 static void move_up(struct cal *cal, int repeat)
   1058 {
   1059 	move_relative(cal, -1);
   1060 }
   1061 
   1062 static void move_down(struct cal *cal, int repeat)
   1063 {
   1064 	move_relative(cal, 1);
   1065 }
   1066 
   1067 static int number_of_hours_in_view(struct cal *cal)
   1068 {
   1069 	time_t st = calendar_loc_to_time(cal, 0);
   1070 	time_t et = calendar_loc_to_time(cal, 1.0);
   1071 
   1072 	return (et - st) / 60 / 60;
   1073 }
   1074 
   1075 
   1076 static void relative_view(struct cal *cal, int hours)
   1077 {
   1078 	time_t current_hour;
   1079 
   1080 	// currently needed because the grid is hour-aligned
   1081 	current_hour = get_hour(cal->current);
   1082 
   1083 	// zt -> cal->current - cal->today
   1084 	cal->start_at = current_hour - cal->today - (hours * 60 * 60);
   1085 	cal->scroll = 0;
   1086 }
   1087 
   1088 
   1089 static void top_view(struct cal *cal)
   1090 {
   1091 	relative_view(cal, 0);
   1092 }
   1093 
   1094 static void bottom_view(struct cal *cal)
   1095 {
   1096 	relative_view(cal, number_of_hours_in_view(cal) - 1);
   1097 }
   1098 
   1099 static void center_view(struct cal *cal)
   1100 {
   1101 	int half_hours = number_of_hours_in_view(cal) / 2;
   1102 	relative_view(cal, half_hours);
   1103 }
   1104 
   1105 static void expand_event(struct event *event, int minutes)
   1106 {
   1107 	icaltimetype dtend =
   1108 		icalcomponent_get_dtend(event->vevent);
   1109 
   1110 	struct icaldurationtype add_minutes =
   1111 		icaldurationtype_from_int(minutes * 60);
   1112 
   1113 	icaltimetype new_dtend =
   1114 		icaltime_add(dtend, add_minutes);
   1115 
   1116 	icalcomponent_set_dtend(event->vevent, new_dtend);
   1117 	// TODO: push down
   1118 }
   1119 
   1120 static void expand_selection_relative(struct cal *cal, int sign)
   1121 {
   1122 	int *step = &cal->timeblock_step;
   1123 	*step = SMALLEST_TIMEBLOCK;
   1124 
   1125 	struct event *event = get_selected_event(cal);
   1126 
   1127 	int size = timeblock_size(cal);
   1128 
   1129 	/* *step = min(*step, size); */
   1130 
   1131 	/* if (sign < 0 && size <= 15) */
   1132 	/* 	*step = 5; */
   1133 	/* else if (sign > 0 && size >= 15) */
   1134 	/* 	*step = 15; */
   1135 
   1136 	int minutes = *step * sign;
   1137 
   1138 	if (size + minutes <= 0)
   1139 		return;
   1140 
   1141 	// no selected event, just expand selector
   1142 	if (event == NULL) {
   1143 		cal->timeblock_size += minutes;
   1144 		return;
   1145 	} else {
   1146 		// expand event if it's selected
   1147 		expand_event(event, minutes);
   1148 	}
   1149 }
   1150 
   1151 static void lock_selection(struct cal *cal)
   1152 {
   1153 	printf("locking event\n");
   1154 	struct event *event = get_selected_event(cal);
   1155 	if (!event)
   1156 		return;
   1157 
   1158 	event->flags ^= EV_IMMOVABLE;
   1159 }
   1160 
   1161 static void expand_selection(struct cal *cal)
   1162 {
   1163 	expand_selection_relative(cal, 1);
   1164 }
   1165 
   1166 static void shrink_selection(struct cal *cal)
   1167 {
   1168 	expand_selection_relative(cal, -1);
   1169 }
   1170 
   1171 static void select_dir(struct cal *cal, int rel)
   1172 {
   1173 	int ind = relative_selection(cal, rel);
   1174 	select_event(cal, ind);
   1175 	if (!timeline_in_view(cal)) {
   1176 		center_view(cal);
   1177 	}
   1178 }
   1179 
   1180 static void select_up(struct cal *cal) 
   1181 {
   1182 	for (int i = 0; i < cal->repeat; i++) {
   1183 		select_dir(cal, -1);
   1184 	}
   1185 }
   1186 
   1187 static void select_down(struct cal *cal)
   1188 {
   1189 	for (int i = 0; i < cal->repeat; i++) {
   1190 		select_dir(cal, 1);
   1191 	}
   1192 }
   1193 
   1194 // TODO: make zoom_amt configurable
   1195 static const int zoom_amt = 1.5;
   1196 
   1197 static void zoom_in(struct cal *cal) {
   1198 	for (int i=0; i < cal->repeat; i++)
   1199 		zoom(cal, -zoom_amt);
   1200 }
   1201 
   1202 static void zoom_out(struct cal *cal) {
   1203 	for (int i=0; i < cal->repeat; i++)
   1204 		zoom(cal, zoom_amt);
   1205 }
   1206 
   1207 static void push_down(struct cal *cal, int ind, time_t push_to)
   1208 {
   1209 	time_t st, et, new_et;
   1210 	struct event *ev;
   1211 
   1212 	ev = &cal->events[ind];
   1213 	vevent_span_timet(ev->vevent, &st, &et);
   1214 
   1215 	if (st >= push_to)
   1216 		return;
   1217 
   1218 	new_et = et - st + push_to;
   1219 
   1220 	if (ind + 1 >= cal->nevents) {
   1221 		if (cal->events[ind+1].flags & EV_IMMOVABLE)
   1222 			return;
   1223 	}
   1224 
   1225 	move_event_to(cal, ev, push_to);
   1226 
   1227 	if (ind + 1 >= cal->nevents)
   1228 		return;
   1229 
   1230 	// push rest
   1231 	push_down(cal, ind+1, new_et);
   1232 }
   1233 
   1234 static void push_up(struct cal *cal, int ind, time_t push_to)
   1235 {
   1236 	time_t st, et, a_st, a_et, new_st;
   1237 	struct event *ev, *above;
   1238 
   1239 	// our event
   1240 	ev = &cal->events[ind];
   1241 	vevent_span_timet(ev->vevent, &st, &et);
   1242 
   1243 	move_event_to(cal, ev, push_to);
   1244 
   1245 	if (ind - 1 < 0)
   1246 		return;
   1247 
   1248 	// above event
   1249 	above = &cal->events[ind - 1 < 0 ? 0 : ind - 1];
   1250 	vevent_span_timet(above->vevent, &a_st, &a_et);
   1251 
   1252 	if (push_to > a_et)
   1253 		return;
   1254 
   1255 	new_st = push_to - (a_et - a_st);
   1256 	push_up(cal, ind-1, new_st);
   1257 }
   1258 
   1259 static void push_expand_selection(struct cal *cal)
   1260 {
   1261 	time_t st, et, push_to, new_st;
   1262 	struct event *ev;
   1263 
   1264 	expand_selection(cal);
   1265 
   1266 	ev = get_selected_event(cal);
   1267 
   1268 	if (ev == NULL)
   1269 		return;
   1270 
   1271 	vevent_span_timet(ev->vevent, &st, &et);
   1272 
   1273 	push_down(cal, cal->selected_event_ind+1, et);
   1274 }
   1275 
   1276 static void pushmove_dir(struct cal *cal, int dir) {
   1277 	time_t st, et, push_to, new_st;
   1278 	struct event *ev;
   1279 
   1280 	ev = get_selected_event(cal);
   1281 
   1282 	if (ev == NULL)
   1283 		return;
   1284 
   1285 	vevent_span_timet(ev->vevent, &st, &et);
   1286 
   1287 	// TODO: configurable?
   1288 	static const int adjust = SMALLEST_TIMEBLOCK * 60;
   1289 
   1290 	push_to = st + (adjust * dir);
   1291 
   1292 	if (dir == 1)
   1293 		push_down(cal, cal->selected_event_ind, push_to);
   1294 	else
   1295 		push_up(cal, cal->selected_event_ind, push_to);
   1296 }
   1297 
   1298 static void pushmove_down(struct cal *cal) {
   1299 	pushmove_dir(cal, 1);
   1300 }
   1301 
   1302 static void pushmove_up(struct cal *cal) {
   1303 	pushmove_dir(cal, -1);
   1304 }
   1305 
   1306 static void open_below(struct cal *cal)
   1307 {
   1308 	time_t st, et;
   1309 	int ind;
   1310 	time_t push_to;
   1311 	struct event *ev;
   1312 
   1313 	ev = get_selected_event(cal);
   1314 
   1315 	if (ev == NULL) {
   1316 		insert_event_after_action(cal);
   1317 		return;
   1318 	}
   1319 
   1320 	vevent_span_timet(ev->vevent, &st, &et);
   1321 
   1322 	push_to = et + timeblock_size(cal) * 60;
   1323 
   1324 	// push down all nearby events
   1325 	// TODO: filter on visible calendars
   1326 	// TODO: don't push down immovable events
   1327 	for (ind = cal->selected_event_ind + 1; ind != -1; ind++) {
   1328 		ind = query_span(cal, ind, et, push_to, et, 0);
   1329 
   1330 		if (ind == -1)
   1331 			break;
   1332 		else
   1333 			push_down(cal, ind, push_to);
   1334 	}
   1335 
   1336 	set_current_calendar(cal, ev->ical);
   1337 
   1338 	insert_event(cal, et, push_to, ev->ical);
   1339 }
   1340 
   1341 
   1342 static void save_calendar(struct ical *calendar)
   1343 {
   1344 	// TODO: caldav saving
   1345 	assert(calendar->source == SOURCE_FILE);
   1346 	printf("DEBUG saving %s\n", calendar->source_location);
   1347 
   1348 	const char *str =
   1349 		icalcomponent_as_ical_string_r(calendar->calendar);
   1350 
   1351 	FILE *fd = fopen(calendar->source_location, "w+");
   1352 
   1353 	fwrite(str, strlen(str), 1, fd);
   1354 
   1355 	fclose(fd);
   1356 }
   1357 
   1358 
   1359 static void finish_editing(struct cal *cal)
   1360 {
   1361 	struct event *event = get_selected_event(cal);
   1362 
   1363 	if (!event)
   1364 		return;
   1365 
   1366 	// TODO: what are we editing?
   1367 	// Right now we can only edit the summary
   1368 
   1369 	// set summary of selected event
   1370 	icalcomponent_set_summary(event->vevent, g_editbuf);
   1371 
   1372 	// leave edit mode, clear inserting flag
   1373 	cal->flags &= ~(CAL_CHANGING | CAL_INSERTING);
   1374 
   1375 	// save the calendar
   1376 	save_calendar(event->ical);
   1377 }
   1378 
   1379 static void append_str_edit_buffer(const char *src)
   1380 {
   1381 	if (*src == '\0')
   1382 		return;
   1383 
   1384 	char *dst = &g_editbuf[g_editbuf_pos];
   1385 	int c = g_editbuf_pos;
   1386 
   1387 	while (c < EDITBUF_MAX && (*dst++ = *src++))
   1388 		c++;
   1389 
   1390 	if (c == EDITBUF_MAX-1)
   1391 		g_editbuf[EDITBUF_MAX-1] = '\0';
   1392 
   1393 	g_editbuf_pos = c;
   1394 
   1395 }
   1396 
   1397 /* static void append_edit_buffer(char key) */
   1398 /* { */
   1399 /* 	if (g_editbuf_pos + 1 >= EDITBUF_MAX) { */
   1400 /* 		warn("attempting to write past end of edit buffer"); */
   1401 /* 		return; */
   1402 /* 	} */
   1403 /* 	g_editbuf[g_editbuf_pos++] = key; */
   1404 /* } */
   1405 
   1406 static void pop_edit_buffer(int amount)
   1407 {
   1408 	amount = clamp(amount, 0, EDITBUF_MAX-1);
   1409 	int top = g_editbuf_pos - amount;
   1410 	top = clamp(top, 0, EDITBUF_MAX-1);
   1411 	g_editbuf[top] = '\0';
   1412 	g_editbuf_pos = top;
   1413 }
   1414 
   1415 static time_t get_selection_end(struct cal *cal)
   1416 {
   1417 	return cal->current + cal->timeblock_size * 60;
   1418 }
   1419 
   1420 static int event_is_today(time_t today, struct event *event)
   1421 {
   1422 	time_t st;
   1423 	vevent_span_timet(event->vevent, &st, NULL);
   1424 	return st < today + DAY_SECONDS;
   1425 }
   1426 
   1427 static void move_event(struct event *event, int minutes)
   1428 {
   1429 	icaltimetype st, et;
   1430 	struct icaldurationtype add;
   1431 
   1432 	st = icalcomponent_get_dtstart(event->vevent);
   1433 	et = icalcomponent_get_dtend(event->vevent);
   1434 
   1435 	add = icaldurationtype_from_int(minutes * 60);
   1436 
   1437 	st = icaltime_add(st, add);
   1438 	et = icaltime_add(et, add);
   1439 
   1440 	icalcomponent_set_dtstart(event->vevent, st);
   1441 	icalcomponent_set_dtend(event->vevent, et);
   1442 }
   1443 
   1444 
   1445 
   1446 static void move_event_action(struct cal *cal, int direction)
   1447 {
   1448 	struct event *event =
   1449 		get_selected_event(cal);
   1450 
   1451 	if (!event)
   1452 		return;
   1453 
   1454 	move_event(event, direction * cal->repeat * SMALLEST_TIMEBLOCK);
   1455 }
   1456 
   1457 static void save_calendars(struct cal *cal)
   1458 {
   1459 	printf("DEBUG saving calendars\n");
   1460 	for (int i = 0; i < cal->ncalendars; ++i)
   1461 		save_calendar(&cal->calendars[i]);
   1462 }
   1463 
   1464 static int closest_to_current(struct cal *cal, int ind_hint)
   1465 {
   1466 	int timeblock = timeblock_size(cal);
   1467 	return query_span(cal, ind_hint-1 < 0 ? 0 : ind_hint-1, cal->current,
   1468 			  cal->current + timeblock * 60, 0, 0);
   1469 }
   1470 
   1471 static void delete_event(struct cal *cal, struct event *event)
   1472 {
   1473 	int i, ind = -1;
   1474 	time_t st;
   1475 	vevent_span_timet(event->vevent, &st, NULL);
   1476 	icalcomponent_remove_component(event->ical->calendar, event->vevent);
   1477 
   1478 	for (i = cal->nevents - 1; i >= 0; i--) {
   1479 		if (&cal->events[i] == event) {
   1480 			ind = i;
   1481 			break;
   1482 		}
   1483 	}
   1484 
   1485 	assert(ind != -1);
   1486 
   1487 	memmove(&cal->events[ind],
   1488 		&cal->events[ind + 1],
   1489 		(cal->nevents - ind - 1) * sizeof(*cal->events));
   1490 
   1491 	// adjust indices
   1492 	cal->nevents--;
   1493 	cal->selected_event_ind = closest_to_current(cal, 0);
   1494 	cal->target--;
   1495 }
   1496 
   1497 // delete the event, and then pull everything below upwards (within that day)
   1498 static void delete_timeblock(struct cal *cal)
   1499 {
   1500 	int first;
   1501 	int i;
   1502 	int timeblock = timeblock_size(cal);
   1503 
   1504 	struct event *event =
   1505 		get_selected_event(cal);
   1506 
   1507 	if (event)
   1508 		delete_event(cal, event);
   1509 
   1510 	// get all events in current day past dtend of current selection
   1511 	time_t starting_at =
   1512 		get_selection_end(cal);
   1513 
   1514 	first =
   1515 		first_event_starting_at(cal, starting_at);
   1516 
   1517 	// nothing to push down today
   1518 	if (first == -1)
   1519 		return;
   1520 
   1521 	for (i = first;
   1522 	     i < cal->nevents && event_is_today(cal->today, &cal->events[i]);
   1523 	     i++) {
   1524 		struct event *event = &cal->events[i];
   1525 		move_event(event, -timeblock);
   1526 	}
   1527 
   1528 	cal->selected_event_ind = closest_to_current(cal, first);
   1529 }
   1530 
   1531 
   1532 static void delete_event_action(struct cal *cal)
   1533 {
   1534 	struct event *event =
   1535 		get_selected_event(cal);
   1536 
   1537 	if (event == NULL)
   1538 		return;
   1539 
   1540 	delete_event(cal, event);
   1541 }
   1542 
   1543 static void cancel_editing(struct cal *cal)
   1544 {
   1545 	// delete the event if we cancel during insert
   1546 	if (cal->flags & CAL_INSERTING) {
   1547 		struct event *event =
   1548 			get_selected_event(cal);
   1549 
   1550 		// we should have a selected event if we're cancelling
   1551 		assert(event);
   1552 
   1553 		delete_event(cal, event);
   1554 	}
   1555 
   1556 	cal->flags &= ~(CAL_CHANGING | CAL_INSERTING);
   1557 }
   1558 
   1559 static void pop_word_edit_buffer()
   1560 {
   1561 	int c = clamp(g_editbuf_pos - 2, 0, EDITBUF_MAX-1);
   1562 	char *p = &g_editbuf[c];
   1563 
   1564 	while (p >= g_editbuf && *(p--) != ' ')
   1565 		;
   1566 
   1567 	if (*(p + 1) == ' ') {
   1568 		p += 2;
   1569 		*p = '\0';
   1570 	}
   1571 	else
   1572 		*(++p) = '\0';
   1573 
   1574 	g_editbuf_pos = p - g_editbuf;
   1575 
   1576 	return;
   1577 }
   1578 
   1579 
   1580 static int on_edit_keypress(struct cal *cal, GdkEventKey *event)
   1581 {
   1582 	char key = *event->string;
   1583 
   1584 	switch (event->keyval) {
   1585 	case GDK_KEY_Escape:
   1586 		cancel_editing(cal);
   1587 		return 1;
   1588 
   1589 	case GDK_KEY_Return:
   1590 		finish_editing(cal);
   1591 		return 1;
   1592 
   1593 	case GDK_KEY_BackSpace:
   1594 		pop_edit_buffer(1);
   1595 		return 1;
   1596 	}
   1597 
   1598 	switch (key) {
   1599 	// Ctrl-w
   1600 	case 0x17:
   1601 		pop_word_edit_buffer();
   1602 		break;
   1603 	}
   1604 
   1605 	// TODO: more special edit keys
   1606 
   1607 	if (*event->string >= 0x20)
   1608 		append_str_edit_buffer(event->string);
   1609 
   1610 	return 1;
   1611 }
   1612 
   1613 static void debug_edit_buffer(GdkEventKey *event)
   1614 {
   1615 	int len = strlen(event->string);
   1616 	printf("DEBUG edit buffer: %s[%x][%ld] %d %d '%s'\n",
   1617 	       event->string,
   1618 	       len > 0 ? *event->string : '\0',
   1619 	       strlen(event->string),
   1620 	       event->state,
   1621 	       g_editbuf_pos,
   1622 	       g_editbuf);
   1623 }
   1624 
   1625 static chord_cmd *get_chord_cmd(char current_chord, char key) {
   1626 	struct chord *chord;
   1627 
   1628 	for (size_t i = 0; i < ARRAY_SIZE(chords); ++i) {
   1629 		chord = &chords[i];
   1630 		if (chord->keys[0] == current_chord && chord->keys[1] == key) {
   1631 			return chord->cmd;
   1632 		}
   1633 	}
   1634 
   1635 	return NULL;
   1636 }
   1637 
   1638 static void set_chord(struct cal *cal, char c)
   1639 {
   1640 	assert(cal->chord == 0);
   1641 	cal->chord = c;
   1642 }
   1643 
   1644 static void move_event_to_calendar(struct cal *cal, struct event *event,
   1645 				   struct ical *from, struct ical *to)
   1646 {
   1647 	icalcomponent_remove_component(from->calendar, event->vevent);
   1648 	icalcomponent_add_component(to->calendar, event->vevent);
   1649 	event->ical = to;
   1650 }
   1651 
   1652 static void next_calendar(struct cal *cal)
   1653 {
   1654 	struct event *event;
   1655 	struct ical *from;
   1656 	struct ical *to;
   1657 
   1658 	from = &cal->calendars[cal->selected_calendar_ind];
   1659 	cal->selected_calendar_ind =
   1660 		(cal->selected_calendar_ind + 1) % cal->ncalendars;
   1661 
   1662 	while((to = &cal->calendars[cal->selected_calendar_ind]) != from && !to->visible) {
   1663 		cal->selected_calendar_ind =
   1664 			(cal->selected_calendar_ind + 1) % cal->ncalendars;
   1665 	}
   1666 
   1667 	// only one selectable calendar
   1668 	if (from == to)
   1669 		return;
   1670 
   1671 	printf("using calendar %s\n", to->source_location);
   1672 
   1673 	// move event to next calendar if we're editing it
   1674 	if (cal->flags & CAL_CHANGING) {
   1675 		event = get_selected_event(cal);
   1676 		if (!event)
   1677 			assert(!"no selected event when CAL_CHANGING");
   1678 
   1679 		move_event_to_calendar(cal, event, from, to);
   1680 	}
   1681 }
   1682 
   1683 static void toggle_calendar_visibility(struct cal *cal, int ind)
   1684 {
   1685 	if (ind+1 > cal->ncalendars)
   1686 		return;
   1687 	cal->calendars[ind].visible =
   1688 		!cal->calendars[ind].visible;
   1689 }
   1690 
   1691 static gboolean on_keypress (GtkWidget *widget, GdkEvent *event,
   1692 			     gpointer user_data)
   1693 {
   1694 	struct extra_data *data = (struct extra_data*)user_data;
   1695 	struct cal *cal = data->cal;
   1696 	char key;
   1697 	int hardware_key;
   1698 	int state_changed = 1;
   1699 	static const int scroll_amt = 60*60;
   1700 
   1701 	switch (event->type) {
   1702 	case GDK_KEY_PRESS:
   1703 		key = *event->key.string;
   1704 		hardware_key = event->key.hardware_keycode;
   1705 
   1706 		printf("DEBUG keystring %x %d hw:%d\n",
   1707 		       key, event->key.state, event->key.hardware_keycode);
   1708 
   1709 		// Ctrl-tab during editing still switch cal
   1710 		if (key != '\t' && (cal->flags & CAL_CHANGING)) {
   1711 			state_changed = on_edit_keypress(cal, &event->key);
   1712 			debug_edit_buffer(&event->key);
   1713 			goto check_state;
   1714 		}
   1715 
   1716 		// handle chords
   1717 		if (cal->chord) {
   1718 			chord_cmd *cmd =
   1719 				get_chord_cmd(cal->chord, key);
   1720 
   1721 			// no chord cmd found, reset chord
   1722 			if (cmd == NULL)
   1723 				cal->chord = 0;
   1724 			else {
   1725 				// execute chord
   1726 				(*cmd)(cal);
   1727 
   1728 				// reset chord
   1729 				cal->chord = 0;
   1730 
   1731 				// we've executed a command, so reset repeat
   1732 				cal->repeat = 1;
   1733 
   1734 				state_changed = 1;
   1735 				goto check_state;
   1736 			}
   1737 		}
   1738 
   1739 		int nkey = key - '0';
   1740 
   1741 		if (nkey >= 2 && nkey <= 9) {
   1742 			printf("DEBUG repeat %d\n", nkey);
   1743 			cal->repeat = nkey;
   1744 			break;
   1745 		}
   1746 
   1747 		switch (hardware_key) {
   1748 			// f1, f2, ...
   1749 		case 67: case 68: case 69:
   1750 		case 70: case 71: case 72:
   1751 			printf("f%d\n", hardware_key-66);
   1752 			int ind = hardware_key-67;
   1753 			assert(ind >= 0);
   1754 			toggle_calendar_visibility(cal, ind);
   1755 			break;
   1756 		}
   1757 
   1758 		switch (key) {
   1759 
   1760 		// Ctrl-d
   1761 		case 0x4:
   1762 			cal->scroll += scroll_amt;
   1763 			break;
   1764 
   1765 		// Ctrl-u
   1766 		case 0x15:
   1767 			cal->scroll -= scroll_amt;
   1768 			break;
   1769 
   1770 		// Ctrl-s
   1771 		case 0x13:
   1772 			save_calendars(cal);
   1773 			break;
   1774 
   1775 		// Ctrl--
   1776 		case 0x2d:
   1777 			cal->font_size -= 2;
   1778 			break;
   1779 
   1780 		// Ctrl-=
   1781 		case 0x3d:
   1782 			cal->font_size += 2;
   1783 			break;
   1784 
   1785 		// tab
   1786 		case '\t':
   1787 			next_calendar(cal);
   1788 			break;
   1789 
   1790 		case 'C':
   1791 		case 'c':
   1792 		case 's':
   1793 		case 'S':
   1794 			edit_mode(cal, EDIT_CLEAR);
   1795 			break;
   1796 
   1797 		case 'A':
   1798 			if (cal->selected_event_ind != -1)
   1799 				edit_mode(cal, 0);
   1800 			break;
   1801 
   1802 		case 'x':
   1803 			delete_event_action(cal);
   1804 			break;
   1805 
   1806 		case 't':
   1807 			move_now(cal);
   1808 			break;
   1809 
   1810 		case 'T':
   1811 			move_event_now(cal);
   1812 			break;
   1813 
   1814 		case 'K':
   1815 			move_event_action(cal, -1);
   1816 			break;
   1817 
   1818 		case 'J':
   1819 			move_event_action(cal, 1);
   1820 			break;
   1821 
   1822 		// Ctrl-j
   1823 		case 0xa:
   1824 			pushmove_down(cal);
   1825 			break;
   1826 
   1827 		case 0xb:
   1828 			pushmove_up(cal);
   1829 			break;
   1830 
   1831 		case 'j':
   1832 			move_down(cal, cal->repeat);
   1833 			break;
   1834 
   1835 		case 'k':
   1836 			move_up(cal, cal->repeat);
   1837 			break;
   1838 
   1839 		case 0x16:
   1840 			push_expand_selection(cal);
   1841 			break;
   1842 
   1843 		case 'l':
   1844 			lock_selection(cal);
   1845 			break;
   1846 
   1847 		case 'v':
   1848 			expand_selection(cal);
   1849 			break;
   1850 
   1851 		case 'V':
   1852 			shrink_selection(cal);
   1853 			break;
   1854 
   1855 		case 'i':
   1856 			insert_event_action(cal);
   1857 			break;
   1858 
   1859 		case 'o':
   1860 			open_below(cal);
   1861 			break;
   1862 
   1863 		default:
   1864 			set_chord(cal, key);
   1865 		}
   1866 
   1867 		//if (key != 0) {
   1868 		//	printf("DEBUG resetting repeat\n");
   1869 		//	cal->repeat = 1;
   1870 		//}
   1871 
   1872 		break;
   1873 	default:
   1874 		state_changed = 0;
   1875 		break;
   1876 	}
   1877 
   1878 check_state:
   1879 	if (state_changed)
   1880 		on_state_change(widget, event, user_data);
   1881 
   1882 	return 1;
   1883 }
   1884 
   1885 static int
   1886 on_press(GtkWidget *widget, GdkEventButton *ev, gpointer user_data) {
   1887 	struct extra_data *data = (struct extra_data*)user_data;
   1888 	struct cal *cal = data->cal;
   1889 	struct event *target = NULL;
   1890 	double mx = ev->x;
   1891 	double my = ev->y;
   1892 	int state_changed = 1;
   1893 
   1894 	switch (ev->type) {
   1895 	case GDK_BUTTON_PRESS:
   1896 		cal->flags |= CAL_MDOWN;
   1897 		cal->target = events_hit(cal->events, cal->nevents, mx, my);
   1898 		target = get_target(cal);
   1899 		if (target) {
   1900 			target->dragy_off = target->y - my;
   1901 			target->dragx_off = target->x - mx;
   1902 		}
   1903 		break;
   1904 	case GDK_BUTTON_RELEASE:
   1905 		target = get_target(cal);
   1906 
   1907 		if ((cal->flags & CAL_DRAGGING) != 0) {
   1908 			// finished drag
   1909 			// TODO: handle drop into and out of gutter
   1910 			calendar_drop(cal, mx, my);
   1911 		}
   1912 		else {
   1913 			// clicked target
   1914 			if (target)
   1915 				event_click(cal, target, mx, my);
   1916 			else if (my < cal->y) {
   1917 				// TODO: gutter clicked, create date event + increase gutter size
   1918 			}
   1919 			else {
   1920 				calendar_view_clicked(cal, mx, my - cal->y);
   1921 			}
   1922 		}
   1923 
   1924 		// finished dragging
   1925 		cal->flags &= ~(CAL_MDOWN | CAL_DRAGGING);
   1926 
   1927 		// clear target drag state
   1928 		if (target) {
   1929 			target->dragx = 0.0;
   1930 			target->dragy = 0.0;
   1931 			target->drag_time =
   1932 				icaltime_as_timet(icalcomponent_get_dtstart(target->vevent));
   1933 			target = NULL;
   1934 		}
   1935 		break;
   1936 
   1937 	default:
   1938 		state_changed = 0;
   1939 		break;
   1940 	}
   1941 
   1942 	if (state_changed)
   1943 		on_state_change(widget, (GdkEvent*)ev, user_data);
   1944 
   1945 	return 1;
   1946 }
   1947 
   1948 static struct event* event_any_flags(struct event *events, int nevents, int flag) {
   1949 	for (int i = 0; i < nevents; i++) {
   1950 		if ((events[i].flags & flag) != 0)
   1951 			return &events[i];
   1952 	}
   1953 	return NULL;
   1954 }
   1955 
   1956 static int
   1957 on_scroll(GtkWidget *widget, GdkEventScroll *ev, gpointer user_data) {
   1958 	// TODO: GtkGestureZoom
   1959 	// https://developer.gnome.org/gtk3/stable/GtkGestureZoom.html
   1960 	struct extra_data *data = (struct extra_data*)user_data;
   1961 	struct cal *cal = data->cal;
   1962 
   1963 	on_state_change(widget, (GdkEvent*)ev, user_data);
   1964 	zoom(cal, ev->delta_y);
   1965 
   1966 	return 0;
   1967 }
   1968 
   1969 static void
   1970 update_event_flags (struct event *ev, double mx, double my) {
   1971 	if (event_hit(ev, mx, my))
   1972 		ev->flags |=  EV_HIGHLIGHTED;
   1973 	else
   1974 		ev->flags &= ~EV_HIGHLIGHTED;
   1975 }
   1976 
   1977 
   1978 static void
   1979 events_update_flags (struct event *events, int nevents, double mx, double my) {
   1980 	for (int i = 0; i < nevents; ++i) {
   1981 		struct event *ev = &events[i];
   1982 		update_event_flags (ev, mx, my);
   1983 	}
   1984 }
   1985 
   1986 
   1987 
   1988 static int
   1989 on_motion(GtkWidget *widget, GdkEventMotion *ev, gpointer user_data) {
   1990 	static struct event* prev_hit = NULL;
   1991 
   1992 	struct event *hit = NULL;
   1993 	struct event *target = NULL;
   1994 
   1995 	int state_changed = 0;
   1996 	int dragging_event = 0;
   1997 	double mx = ev->x;
   1998 	double my = ev->y;
   1999 
   2000 	struct extra_data *data = (struct extra_data*)user_data;
   2001 	struct cal *cal = data->cal;
   2002 	GdkWindow *gdkwin = gtk_widget_get_window(widget);
   2003 
   2004 	cal->mx = mx - cal->x;
   2005 	cal->my = my - cal->y;
   2006 
   2007 	double px = ev->x;
   2008 	double py = ev->y;
   2009 
   2010 	// drag detection
   2011 	if ((cal->flags & CAL_MDOWN) != 0)
   2012 	if ((cal->flags & CAL_DRAGGING) == 0)
   2013 		cal->flags |= CAL_DRAGGING;
   2014 
   2015 		// dragging logic
   2016 	if ((cal->flags & CAL_DRAGGING) != 0) {
   2017 		target = get_target(cal);
   2018 		if (target) {
   2019 			dragging_event = 1;
   2020 			target->dragx = px - target->x;
   2021 			target->dragy =
   2022 				target->dragy_off + py - target->y - cal->y;
   2023 		}
   2024 	}
   2025 
   2026 	events_update_flags (cal->events, cal->nevents, mx, my);
   2027 	hit = event_any_flags(cal->events, cal->nevents, EV_HIGHLIGHTED);
   2028 
   2029 	gdk_window_set_cursor(gdkwin, hit ? cursor_pointer : cursor_default);
   2030 
   2031 	state_changed = dragging_event || hit != prev_hit;
   2032 
   2033 	fflush(stdout);
   2034 	prev_hit = hit;
   2035 
   2036 	if (state_changed)
   2037 		on_state_change(widget, (GdkEvent*)ev, user_data);
   2038 
   2039 	return 1;
   2040 }
   2041 
   2042 
   2043 static void
   2044 format_margin_time(char *buffer, int bsize, int hour) {
   2045 	struct tm tm = { .tm_min = 0, .tm_hour = hour };
   2046 	strftime(buffer, bsize, "%X", &tm);
   2047 	time_remove_seconds(buffer, 1);
   2048 }
   2049 
   2050 
   2051 static double
   2052 time_to_location (time_t start, time_t end, time_t time) {
   2053 	return ((double)(time - start) / ((double)(end - start)));
   2054 }
   2055 
   2056 
   2057 
   2058 static double
   2059 calendar_time_to_loc(struct cal *cal, time_t time) {
   2060 	// ZOOM
   2061 	return time_to_location(calendar_view_start(cal),
   2062 				calendar_view_end(cal), time) * cal->zoom;
   2063 }
   2064 
   2065 
   2066 
   2067 static double calendar_time_to_loc_absolute(struct cal *cal, time_t time) {
   2068 	return calendar_time_to_loc(cal, time) * cal->height + cal->y;
   2069 }
   2070 
   2071 
   2072 
   2073 static void
   2074 event_update (struct event *ev, struct cal *cal)
   2075 {
   2076 	icaltimetype dtstart = icalcomponent_get_dtstart(ev->vevent);
   2077 	/* icaltimetype dtend = icalcomponent_get_dtend(ev->vevent); */
   2078 	int isdate = dtstart.is_date;
   2079 	double sx, sy, y, eheight, height, width;
   2080 
   2081 
   2082 	height = cal->height;
   2083 	width = cal->width;
   2084 
   2085 	sx = cal->x;
   2086 	sy = cal->y;
   2087 
   2088 	// height is fixed in top gutter for date events
   2089 	if (isdate) {
   2090 		// TODO: (DATEEV) gutter positioning
   2091 		eheight = 20.0;
   2092 		y = EVPAD;
   2093 	}
   2094 	else {
   2095 		// convert to local time
   2096 		time_t st, et;
   2097 		vevent_span_timet(ev->vevent, &st, &et);
   2098 
   2099 		double sloc = calendar_time_to_loc(cal, st);
   2100 		double eloc = calendar_time_to_loc(cal, et);
   2101 
   2102 		double dloc = eloc - sloc;
   2103 		eheight = dloc * height;
   2104 		y = (sloc * height) + sy;
   2105 	}
   2106 
   2107 	ev->width = width;
   2108 	ev->height = eheight;
   2109 	ev->x = sx;
   2110 	ev->y = y;
   2111 }
   2112 
   2113 static void
   2114 update_calendar (struct cal *cal) {
   2115 	int i, width, height;
   2116 	width = cal->width;
   2117 	height = cal->height;
   2118 
   2119 	width  -= cal->x;
   2120 	height -= cal->y * 2;
   2121 
   2122 	if (cal->refresh_events) {
   2123 		on_change_view(cal);
   2124 		cal->refresh_events = 0;
   2125 	}
   2126 
   2127 	for (i = 0; i < cal->nevents; ++i) {
   2128 		struct event *ev = &cal->events[i];
   2129 		event_update(ev, cal);
   2130 	}
   2131 }
   2132 
   2133 
   2134 
   2135 
   2136 static void draw_rectangle (cairo_t *cr, double x, double y) {
   2137 	cairo_rel_line_to (cr, x, 0);
   2138 	cairo_rel_line_to (cr, 0, y);
   2139 	cairo_rel_line_to (cr, -x, 0);
   2140 	cairo_close_path (cr);
   2141 }
   2142 
   2143 
   2144 static void draw_background (cairo_t *cr, int width, int height) {
   2145 	cairo_set_source_rgb (cr, 0.3, 0.3, 0.3);
   2146 	draw_rectangle (cr, width, height);
   2147 	cairo_fill (cr);
   2148 }
   2149 
   2150 
   2151 static void
   2152 draw_hours (cairo_t *cr, struct cal* cal)
   2153 {
   2154 	double height = cal->height;
   2155 	double width  = cal->width;
   2156 	double zoom   = cal->zoom;
   2157 
   2158 	double section_height = (((double)height) / 48.0) * zoom;
   2159 	char buffer[32] = {0};
   2160 	const double col = 0.4;
   2161 	cairo_set_source_rgb (cr, col, col, col);
   2162 
   2163 	// TODO: dynamic section subdivide on zoom?
   2164 	for (int section = 0; section < 48; section++) {
   2165 		int start_section = ((cal->start_at + cal->scroll) / 60 / 60) * 2;
   2166 		int minutes = (start_section + section) * 30;
   2167 		int onhour = ((minutes / 30) % 2) == 0;
   2168 		int hour = (minutes / 60) % 24;
   2169 		int onday = onhour && hour == 0;
   2170 
   2171 		if (section_height < 14 && !onhour)
   2172 			continue;
   2173 
   2174 		double y = cal->y + ((double)section) * section_height;
   2175 
   2176 		cairo_set_line_width (cr, onday ? 4 : 1);
   2177 		cairo_move_to (cr, cal->x, y);
   2178 		cairo_rel_line_to (cr, width, 0);
   2179 
   2180 		if (section % 2 == 0)
   2181 			cairo_set_dash (cr, NULL, 0, 0);
   2182 		else
   2183 			cairo_set_dash (cr, dashed, 1, 0);
   2184 
   2185 		cairo_stroke(cr);
   2186 		cairo_set_dash (cr, NULL, 0, 0);
   2187 
   2188 		if (onhour) {
   2189 			format_margin_time(buffer, 32, hour);
   2190 			// TODO: text extents for proper time placement?
   2191 			cairo_move_to(cr, g_lmargin - (g_margin_time_w + EVPAD),
   2192 				      y+TXTPAD);
   2193 			cairo_set_source_rgb (cr,
   2194 						g_text_color.r,
   2195 						g_text_color.g,
   2196 						g_text_color.b);
   2197 			cairo_show_text(cr, buffer);
   2198 			cairo_set_source_rgb (cr, col, col, col);
   2199 		}
   2200 	}
   2201 }
   2202 
   2203 static void
   2204 format_time_duration(char *buf, int bufsize, int seconds)
   2205 {
   2206 	int hours = seconds / 60 / 60;
   2207 	seconds -= hours * 60 * 60;
   2208 
   2209 	int minutes = seconds / 60;
   2210 	seconds -= minutes * 60;
   2211 
   2212 	if (hours == 0 && minutes == 0)
   2213 		snprintf(buf, bufsize, "%ds", seconds);
   2214 	else if (hours != 0 && minutes == 0)
   2215 		snprintf(buf, bufsize, "%dh", hours);
   2216 	else if (hours == 0 && minutes != 0)
   2217 		snprintf(buf, bufsize, "%dm", minutes);
   2218 	else
   2219 		snprintf(buf, bufsize, "%dh%dm", hours, minutes);
   2220 }
   2221 
   2222 #define  Pr  .299
   2223 #define  Pg  .587
   2224 #define  Pb  .114
   2225 static void desaturate(union rgba *c, double change)
   2226 {
   2227 	double  P=sqrt(
   2228 		(c->r)*(c->r)*Pr+
   2229 		(c->g)*(c->g)*Pg+
   2230 		(c->b)*(c->b)*Pb ) ;
   2231 
   2232 	c->r = P+((c->r)-P)*change;
   2233 	c->g = P+((c->g)-P)*change;
   2234 	c->b = P+((c->b)-P)*change;
   2235 }
   2236 
   2237 static void saturate(union rgba *c, double change)
   2238 {
   2239 	double L = Pr * c->r + 
   2240 		   Pg * c->g + 
   2241 		   Pb * c->b;
   2242 
   2243 	c->r += change * (L - c->r);
   2244 	c->g += change * (L - c->g);
   2245 	c->b += change * (L - c->b);
   2246 }
   2247 
   2248 static double get_evheight(double evheight)
   2249 {
   2250 	return max(1.0, evheight - EVMARGIN);
   2251 }
   2252 
   2253 static void
   2254 draw_event_summary(cairo_t *cr, struct cal *cal, time_t st, time_t et,
   2255 		   int is_date, int is_selected, double height, const char *summary,
   2256 		   struct event *sel, double x, double y, union rgba color)
   2257 {
   2258 	// TODO: event text color
   2259 	static char buffer[1024] = {0};
   2260 	static char bsmall[32] = {0};
   2261 	static char bsmall2[32] = {0};
   2262 	const char *fmt;
   2263 	char *start_time;
   2264 	char *end_time;
   2265 	time_t len = et - st;
   2266 
   2267 	//desaturate(&color, 0.8);
   2268 	double c = 0.9;
   2269 	color.r = c;
   2270 	color.g = c;
   2271 	color.b = c;
   2272 
   2273 	cairo_text_extents_t exts;
   2274 
   2275 	int is_editing = is_selected && (cal->flags & CAL_CHANGING);
   2276 
   2277 	summary = is_editing ? g_editbuf : summary;
   2278 
   2279 	cairo_set_source_rgb(cr, color.r, color.g, color.b);
   2280 
   2281 	if (is_date) {
   2282 		sprintf(buffer, is_selected ? "'%s'" : "%s", summary);
   2283 		cairo_text_extents(cr, buffer, &exts);
   2284 		cairo_move_to(cr, x + EVPAD, y + (height / 2.0)
   2285 						+ ((double)exts.height / 2.0));
   2286 		cairo_show_text(cr, buffer);
   2287 
   2288 		return;
   2289 	}
   2290 
   2291 	start_time = format_locale_timet(bsmall, 32, st);
   2292 	end_time   = format_locale_timet(bsmall2, 32, et);
   2293 	// TODO: configurable event format
   2294 	char duration_format[32] = {0};
   2295 	char duration_format_in[32] = {0};
   2296 	char duration_format_out[32] = {0};
   2297 	time_t now, in, out;
   2298 	time(&now);
   2299 
   2300 	in = now - st;
   2301 	out = et - now;
   2302 
   2303 	format_time_duration(duration_format,
   2304 			     sizeof(duration_format), len);
   2305 
   2306 	format_time_duration(duration_format_in,
   2307 			     sizeof(duration_format), in);
   2308 
   2309 	format_time_duration(duration_format_out,
   2310 			     sizeof(duration_format), out);
   2311 
   2312 	sprintf(buffer, is_editing ? "'%s'" : "%s", summary);
   2313 	cairo_text_extents(cr, buffer, &exts);
   2314 	double ey = height < exts.height
   2315 		? y + TXTPAD - EVPAD
   2316 		: y + TXTPAD + EVPAD;
   2317 	cairo_move_to(cr, x + EVPAD, ey);
   2318 	cairo_show_text(cr, buffer);
   2319 
   2320 	if (out >= 0 && in >= 0 && out < len) {
   2321 		sprintf(buffer, "%s-%s +%s-%s %s", start_time, end_time,
   2322 			duration_format_in, duration_format_out, duration_format);
   2323 	} else if (in >= 0 && in < 0) {
   2324 		sprintf(buffer, "%s-%s %s +%s", start_time, end_time,
   2325 			duration_format, duration_format_in);
   2326 	} else {
   2327 		sprintf(buffer, "%s-%s %s", start_time, end_time, duration_format);
   2328 	}
   2329 
   2330 	ey += exts.height + 4;
   2331 
   2332 	double tadj = 0.8;
   2333 	cairo_move_to(cr, x + EVPAD, ey);
   2334 	cairo_set_source_rgb(cr, color.r * tadj, color.g * tadj, color.b * tadj);
   2335 	cairo_show_text(cr, buffer);
   2336 }
   2337 
   2338 static void
   2339 draw_event (cairo_t *cr, struct cal *cal, struct event *ev,
   2340 	    struct event *sel, struct event *target)
   2341 {
   2342 	union rgba c = ev->ical->color;
   2343 	desaturate(&c, 0.4);
   2344 
   2345 	int is_locked = ev->flags & EV_IMMOVABLE;
   2346 	int is_dragging = target == ev && (cal->flags & CAL_DRAGGING);
   2347 	int is_selected = sel == ev;
   2348 	icaltimetype dtstart =
   2349 		icalcomponent_get_dtstart(ev->vevent);
   2350 
   2351 	time_t st, et;
   2352 	vevent_span_timet(ev->vevent, &st, &et);
   2353 
   2354 	double x = ev->x;
   2355 	double y = ev->y;
   2356 	double evheight = get_evheight(ev->height);
   2357 
   2358 	const char *summary =
   2359 		icalcomponent_get_summary(ev->vevent);
   2360 
   2361 	if (is_dragging || ev->flags & EV_HIGHLIGHTED) {
   2362 		c.a *= 0.95;
   2363 	}
   2364 
   2365 	if (is_locked) {
   2366 		c.r *= 0.8;
   2367 	}
   2368 
   2369 	// grid logic
   2370 	if (is_dragging) {
   2371 		/* x += ev->dragx; */
   2372 		y += ev->dragy;
   2373 		st = closest_timeblock(cal, y);
   2374 		y = calendar_time_to_loc_absolute(cal, st);
   2375 		target->drag_time = st;
   2376 	}
   2377 
   2378 	/* y -= EVMARGIN; */
   2379 
   2380 	cairo_move_to(cr, x, y);
   2381 
   2382 	// TODO: selected event rendering
   2383 	if (is_selected)
   2384 		c.r *= 0.8;
   2385 
   2386 	cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a);
   2387 	draw_rectangle(cr, ev->width, evheight);
   2388 	cairo_fill(cr);
   2389 	draw_event_summary(cr, cal, st, et, dtstart.is_date, is_selected,
   2390 			   evheight, summary, sel, x, y, ev->ical->color);
   2391 }
   2392 
   2393 
   2394 static inline void
   2395 draw_line (cairo_t *cr, double x, double y, double w) {
   2396 	cairo_move_to(cr, x, y + 0.5);
   2397 	cairo_rel_line_to(cr, w, 0);
   2398 }
   2399 
   2400 
   2401 
   2402 static void
   2403 draw_time_line(cairo_t *cr, struct cal *cal, time_t time) {
   2404 	double y = calendar_time_to_loc_absolute(cal, time);
   2405 	int w = cal->width;
   2406 
   2407 	cairo_set_line_width(cr, 1.0);
   2408 
   2409 	cairo_set_source_rgb (cr, 1.0, 0, 0);
   2410 	draw_line(cr, cal->x, y - 1, w);
   2411 	cairo_stroke(cr);
   2412 
   2413 	/* cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); */
   2414 	/* draw_line(cr, cal->x, y, w); */
   2415 	/* cairo_stroke(cr); */
   2416 
   2417 	/* cairo_set_source_rgb (cr, 0, 0, 0); */
   2418 	/* draw_line(cr, cal->x, y + 1, w); */
   2419 	/* cairo_stroke(cr); */
   2420 }
   2421 
   2422 static void
   2423 draw_selection (cairo_t *cr, struct cal *cal)
   2424 {
   2425 	static const char *summary = "Selection";
   2426 	static const int is_selected = 0;
   2427 	static const int is_date = 0;
   2428 	double sx = cal->x;
   2429 	double sy = calendar_time_to_loc_absolute(cal, cal->current);
   2430 	time_t et = get_selection_end(cal);
   2431 	double height = calendar_time_to_loc_absolute(cal, et) - sy;
   2432 
   2433 	cairo_move_to(cr, sx, sy);
   2434 
   2435 	cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.4);
   2436 	draw_rectangle(cr, cal->width, height);
   2437 	cairo_fill(cr);
   2438 	draw_event_summary(cr, cal, cal->current, et, is_date, is_selected,
   2439 			   height, summary, NULL, sx, sy, ((union rgba){0.1,0.1,0.1}));
   2440 
   2441 }
   2442 
   2443 static void draw_current_minute(cairo_t *cr, struct cal *cal)
   2444 {
   2445 	char buffer[32] = {0};
   2446 	time_t now;
   2447 	time(&now);
   2448 
   2449 	double y = calendar_time_to_loc_absolute(cal, now);
   2450 	const double col = 0.4; // TODO: duplication from draw_hours
   2451 
   2452 	int min = get_minute(now);
   2453 
   2454 	format_margin_time(buffer, 32, min);
   2455 
   2456 	cairo_set_source_rgb (cr, 1.0, 0, 0);
   2457 
   2458 	cairo_move_to(cr, g_lmargin - (g_margin_time_w + EVPAD),
   2459 		      y+(TXTPAD/2.0)-2.0);
   2460 
   2461 	cairo_show_text(cr, buffer);
   2462 	cairo_set_source_rgb (cr, col, col, col);
   2463 }
   2464 
   2465 static int
   2466 draw_calendar (cairo_t *cr, struct cal *cal) {
   2467 	int i, width, height;
   2468 	time_t now;
   2469 	width = cal->width;
   2470 	height = cal->height;
   2471 
   2472 	cairo_move_to(cr, cal->x, cal->y);
   2473 	draw_background(cr, width, height);
   2474 	draw_hours(cr, cal);
   2475 	draw_current_minute(cr, cal);
   2476 
   2477 	struct event *selected =
   2478 		get_selected_event(cal);
   2479 
   2480 	// draw calendar events
   2481 	for (i = 0; i < cal->nevents; ++i) {
   2482 		struct event *ev = &cal->events[i];
   2483 		if (ev->ical->visible)
   2484 			draw_event(cr, cal, ev, selected, get_target(cal));
   2485 	}
   2486 
   2487 	if (cal->selected_event_ind == -1)
   2488 		draw_selection(cr, cal);
   2489 
   2490 	draw_time_line(cr, cal, time(&now));
   2491 
   2492 	return 1;
   2493 }
   2494 
   2495 static gboolean
   2496 on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data)
   2497 {
   2498 	int width, height;
   2499 	struct extra_data *data = (struct extra_data*) user_data;
   2500 	struct cal *cal = data->cal;
   2501 
   2502 	if (!margin_calculated) {
   2503 		char buffer[32];
   2504 		cairo_text_extents_t exts;
   2505 
   2506 		format_margin_time(buffer, 32, 23);
   2507 		cairo_text_extents(cr, buffer, &exts);
   2508 		g_margin_time_w = exts.width;
   2509 		g_lmargin = g_margin_time_w + EVPAD*2;
   2510 
   2511 		margin_calculated = 1;
   2512 	}
   2513 
   2514 	cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
   2515 	cairo_set_font_size(cr, cal->font_size);
   2516 	cairo_select_font_face(cr,
   2517 				"terminus",
   2518 				CAIRO_FONT_SLANT_NORMAL,
   2519 				CAIRO_FONT_WEIGHT_NORMAL);
   2520 
   2521 	gtk_window_get_size(data->win, &width, &height);
   2522 
   2523 	cal->y = cal->gutter_height;
   2524 
   2525 	cal->width = width - cal->x;
   2526 	cal->height = height - cal->y;
   2527 
   2528 	update_calendar(cal);
   2529 	draw_calendar(cr, cal);
   2530 
   2531 	return FALSE;
   2532 }
   2533 
   2534 
   2535 void usage() {
   2536 	printf("usage: viscal <calendar.ics ...>\n");
   2537 	exit(1);
   2538 }
   2539 
   2540 static inline double rand_0to1() {
   2541 	return (double) rand() / RAND_MAX;
   2542 }
   2543 
   2544 static gboolean redraw_timer_handler(struct extra_data *data) {
   2545 	gtk_widget_queue_draw(data->cal->widget);
   2546 	return 1;
   2547 }
   2548 
   2549 
   2550 int main(int argc, char *argv[])
   2551 {
   2552 	GtkWidget *window;
   2553 	GtkWidget *darea;
   2554 	GdkDisplay *display;
   2555 	GdkColor color;
   2556 	char buffer[32];
   2557 	double text_col = 0.6;
   2558 	struct ical *ical;
   2559 	union rgba defcol;
   2560 
   2561 	defcol.r = 106.0 / 255.0;
   2562 	defcol.g = 219.0 / 255.0;
   2563 	defcol.b = 219.0 / 255.0;
   2564 	defcol.a = 1.0;
   2565 
   2566 	struct cal cal;
   2567 
   2568 	calendar_create(&cal);
   2569 
   2570 	if (argc < 2)
   2571 		usage();
   2572 
   2573 	srand(0);
   2574 
   2575 	for (int i = 1; i < argc; i++) {
   2576 		printf("loading calendar %s\n", argv[i]);
   2577 		ical = calendar_load_ical(&cal, argv[i]);
   2578 		ical->source = SOURCE_FILE;
   2579 		ical->source_location = argv[i];
   2580 
   2581 		// TODO: configure colors from cli?
   2582 		if (ical != NULL) {
   2583 			ical->color = defcol;
   2584 			ical->color.r = rand_0to1() > 0.5 ? 1.0 : 0;
   2585 			ical->color.g = rand_0to1() > 0.5 ? 1.0 : 0;
   2586 			ical->color.b = rand_0to1() > 0.5 ? 1.0 : 0;
   2587 			ical->color.a = 0.9;
   2588 
   2589 			//saturate(&ical->color, 0.35);
   2590 		}
   2591 		else {
   2592 			printf("failed to load calendar\n");
   2593 		}
   2594 	}
   2595 
   2596 
   2597 	on_change_view(&cal);
   2598 	//select_closest_to_now(&cal);
   2599 
   2600 	// TODO: get system timezone
   2601 	g_cal_tz = cal.tz = icaltimezone_get_builtin_timezone("America/Vancouver");
   2602 	tz_utc = icaltimezone_get_builtin_timezone("UTC");
   2603 
   2604 	g_text_color.r = text_col;
   2605 	g_text_color.g = text_col;
   2606 	g_text_color.b = text_col;
   2607 
   2608 	color.red = BGCOLOR * 0xffff * 0.6;
   2609 	color.green = BGCOLOR * 0xffff * 0.6;
   2610 	color.blue = BGCOLOR * 0xffff * 0.6;
   2611 
   2612 	/* setlocale(LC_TIME, ""); */
   2613 
   2614 	// calc margin
   2615 	format_margin_time(buffer, 32, 12);
   2616 
   2617 	gtk_init(&argc, &argv);
   2618 
   2619 	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   2620 
   2621 	struct extra_data extra_data = {
   2622 		.win = GTK_WINDOW(window),
   2623 		.cal = &cal
   2624 	};
   2625 
   2626 	display = gdk_display_get_default();
   2627 	darea = gtk_drawing_area_new();
   2628 	cal.widget = darea;
   2629 	gtk_container_add(GTK_CONTAINER(window), darea);
   2630 
   2631 	cursor_pointer = gdk_cursor_new_from_name (display, "pointer");
   2632 	cursor_default = gdk_cursor_new_from_name (display, "default");
   2633 
   2634 	// redraw timer
   2635 	g_timeout_add(500, (GSourceFunc)redraw_timer_handler,
   2636 		      (gpointer)&extra_data);
   2637 
   2638 	g_signal_connect(G_OBJECT(darea), "button-press-event",
   2639 			G_CALLBACK(on_press), (gpointer)&extra_data);
   2640 
   2641 	g_signal_connect(window, "key-press-event",
   2642 			 G_CALLBACK(on_keypress), (gpointer)&extra_data);
   2643 
   2644 	g_signal_connect(G_OBJECT(darea), "button-release-event",
   2645 			G_CALLBACK(on_press), (gpointer)&extra_data);
   2646 
   2647 	g_signal_connect(G_OBJECT(darea), "motion-notify-event",
   2648 			G_CALLBACK(on_motion), (gpointer)&extra_data);
   2649 
   2650 	g_signal_connect(G_OBJECT(darea), "scroll-event",
   2651 			G_CALLBACK(on_scroll), (gpointer)&extra_data);
   2652 
   2653 	g_signal_connect(G_OBJECT(darea), "draw",
   2654 			G_CALLBACK(on_draw_event), (gpointer)&extra_data);
   2655 
   2656 	g_signal_connect(window, "destroy",
   2657 			 G_CALLBACK(gtk_main_quit), NULL);
   2658 
   2659 	gtk_widget_set_events(darea, GDK_BUTTON_PRESS_MASK
   2660 				| GDK_BUTTON_RELEASE_MASK
   2661 				| GDK_KEY_PRESS_MASK
   2662 				| GDK_KEY_RELEASE_MASK
   2663 				| GDK_SCROLL_MASK
   2664 				| GDK_SMOOTH_SCROLL_MASK
   2665 				| GDK_POINTER_MOTION_MASK);
   2666 
   2667 	gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
   2668 	gtk_window_set_default_size(GTK_WINDOW(window), 400, 800);
   2669 	gtk_window_set_title(GTK_WINDOW(window), "viscal");
   2670 
   2671 	// TODO: proper css/gtk styling?
   2672 	gtk_widget_modify_bg(window, GTK_STATE_NORMAL, &color);
   2673 	gtk_widget_show_all(window);
   2674 
   2675 	gtk_main();
   2676 
   2677 	return 0;
   2678 }