viscal

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

viscal.c (59887B)


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