viscal

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

commit 4a03ae99434e5d86925386af38394a41ddb9cc18
parent 9015275c543ea6011cb0cd4345c9fca57d2d3aa1
Author: William Casarin <bill@casarin.me>
Date:   Mon, 16 Jan 2017 21:58:40 -0800

ical event loading working

Diffstat:
MMakefile | 1+
Mcalendar.c | 307++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
2 files changed, 202 insertions(+), 106 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,6 +7,7 @@ DEPS=libical gtk+-3.0 CFLAGS=-Wall \ -Wextra \ + -O0 \ -Wno-unused-parameter \ -Werror=int-conversion \ -std=c99 \ diff --git a/calendar.c b/calendar.c @@ -2,6 +2,7 @@ #include <cairo/cairo.h> #include <gtk/gtk.h> #include <libical/ical.h> +#include <assert.h> #include <time.h> #include <string.h> #include <stdlib.h> @@ -10,6 +11,25 @@ #define length(array) (sizeof((array))/sizeof((array)[0])) +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +static const double BGCOLOR = 0.35; +static const int DAY_SECONDS = 86400; +static const int MAX_EVENTS = 1024; +static const int TXTPAD = 11; +static const int EVPAD = 2; +static const int GAP = 0; +static const int DEF_LMARGIN = 20; + + enum event_flags { EV_SELECTED = 1 << 0 , EV_HIGHLIGHTED = 1 << 1 @@ -21,15 +41,32 @@ enum cal_flags { , CAL_DRAGGING = 1 << 1 }; +struct event { + icalcomponent *vevent; + icalcomponent *calendar; + + enum event_flags flags; + // set on draw + double width, height; + double x, y; + double dragx, dragy; + time_t drag_time; +}; + struct cal { - struct icalcomponent *calendars[128]; + icalcomponent * calendars[128]; int ncalendars; + struct event events[MAX_EVENTS]; + int nevents; + enum cal_flags flags; // TODO: make multiple target selection struct event *target; int minute_round; - time_t view; + + time_t view_start; + time_t view_end; }; struct extra_data { @@ -37,19 +74,6 @@ struct extra_data { struct cal *cal; }; -struct event { - time_t start; - time_t end; - char *summary; - enum event_flags flags; - - // set on draw - double width, height; - double x, y; - double dragx, dragy; - time_t drag_time; -}; - union rgba { double rgba[4]; struct { @@ -57,57 +81,54 @@ union rgba { }; }; -#define max(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) - -#define min(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) - -static const double BGCOLOR = 0.35; -static const int DAY_SECONDS = 86400; -static const int TXTPAD = 11; -static const int EVPAD = 2; -static const int GAP = 0; -static const int DEF_LMARGIN = 20; - static int g_lmargin = 40; +static icaltimezone *g_timezone; static int g_margin_time_w = 0; static union rgba g_text_color; static int margin_calculated = 0; static GdkCursor *cursor_pointer; static GdkCursor *cursor_default; -const double dashed[] = {1.0}; - +static const double dashed[] = {1.0}; static struct event* events_hit (struct event *, int, double, double); static int event_hit (struct event *, double, double); -static icalcomponent* calendar_load_ical(struct cal *cal, char *path); -static void calendar_print_state(struct cal *cal); -static void calendar_create(struct cal *cal); + +static icalcomponent* calendar_load_ical(struct cal *, char *); +static void calendar_print_state(struct cal *); +static void calendar_create(struct cal *); +static void calendar_update (struct cal *, int, int); +static int calendar_draw (cairo_t *, struct cal*, int, int); + static void format_margin_time (char *, int, int); -static void format_locale_time(char *buffer, int bsize, struct tm *tm); +static void format_locale_time(char *, int, struct tm *); static void draw_hours (cairo_t *, int, int, int); static void draw_background (cairo_t *, int, int); static void draw_rectangle (cairo_t *, double, double); -static void event_update (struct event *, time_t, int, int, int, int); + +static int vevent_in_view(icalcomponent *, time_t, time_t); +static void events_for_view(struct cal *, time_t, time_t); +static void event_update (struct event *, time_t, time_t, int, int, int, int); static void event_draw (cairo_t *, struct cal*, struct event *, int); -static void update_events_flags (struct event*, int, double, double); -static void calendar_update (struct cal *cal, int width, int height); -static int calendar_draw (cairo_t *, struct cal*, int, int); +static inline icaltime_span event_get_span (struct event*); +static void events_update_flags (struct event*, int, double, double); + static gboolean on_draw_event(GtkWidget *widget, cairo_t *cr, gpointer user_data); +static void on_change_view(struct cal*); + static int on_press(GtkWidget *widget, GdkEventButton *ev, gpointer user_data); static int on_motion(GtkWidget *widget, GdkEventMotion *ev, gpointer user_data); static int on_state_change(GtkWidget *widget, GdkEvent *ev, gpointer user_data); static void calendar_create(struct cal *cal) { + time_t now; + time_t today; + int start_at = 0; + struct tm nowtm; + now = time(NULL); nowtm = *localtime(&now); nowtm.tm_hour = 0; @@ -115,9 +136,15 @@ calendar_create(struct cal *cal) { today = mktime(&nowtm); cal->ncalendars = 0; + cal->nevents = 0; cal->minute_round = 30; - cal->view = today; + cal->view_start = today + start_at; + cal->view_end = today + DAY_SECONDS; +} +static void +on_change_view(struct cal *cal) { + events_for_view(cal, cal->view_start, cal->view_end); } static int @@ -131,14 +158,6 @@ on_state_change(GtkWidget *widget, GdkEvent *ev, gpointer user_data) { return 1; } -static struct event * -event_create(struct event *ev) { - ev->dragx = 0; - ev->dragy = 0; - ev->summary = ""; - return ev; -} - static char * file_load(char *path) { FILE *f = fopen(path, "rb"); @@ -152,41 +171,115 @@ file_load(char *path) { return string; } +static int +span_overlaps(time_t start1, time_t end1, time_t start2, time_t end2) { + return max(0, min(end1, end2) - max(start1, start2)); +} + +static int +vevent_in_view(icalcomponent *vevent, time_t start, time_t end) { + icaltime_span span = icalcomponent_get_span(vevent); + return span_overlaps(span.start, span.end, start, end); +} + static void -event_from_vevent(struct *event event, icalcomponent *vevent) { - event->summary = icalcomponent_get_summary(vevent); - event->start = icaltime_as_timet(icalcomponent_get_dtstart(vevent)); - event->end = icaltime_as_timet(icalcomponent_get_dtend(vevent)); +events_for_view(struct cal *cal, time_t start, time_t end) +{ + int i, count = 0; + struct event *event; + icalcomponent *vevent; + icalcomponent *ical; + + for (i = 0; i < cal->ncalendars; ++i) { + ical = cal->calendars[i]; + for (vevent = icalcomponent_get_first_component(ical, ICAL_VEVENT_COMPONENT); + vevent != NULL && count < MAX_EVENTS; + vevent = icalcomponent_get_next_component(ical, ICAL_VEVENT_COMPONENT)) + { + if (vevent_in_view(vevent, start, end)) { + event = &cal->events[count++]; + /* printf("event in view %s\n", icalcomponent_get_summary(vevent)); */ + event->vevent = vevent; + event->calendar = ical; + } + } + cal->nevents = count; + } } + static icalcomponent * -calendar_load_ical(char *path) { +calendar_load_ical(struct cal *cal, char *path) { + // TODO: don't load duplicate calendars + icalcomponent *prop; - icalcomponent_kind kind = ICAL_VEVENT_COMPONENT; // TODO: free icalcomponent somewhere const char *str = file_load(path); icalcomponent *calendar = icalparser_parse_string(str); - if (!component) return NULL; + if (!calendar) return NULL; + + // TODO: support >128 calendars + if (length(cal->calendars) == cal->ncalendars) + return NULL; - cal->calendars[cal->calendars++] = calendar; + cal->calendars[cal->ncalendars++] = calendar; free((void*)str); return calendar; } + +static void +event_set_start(struct event *ev, time_t time, const icaltimezone *zone) { + if (zone == NULL) + zone = g_timezone; + icaltimetype ictime = icaltime_from_timet_with_zone(time, 1, zone); + icalcomponent_set_dtstart(ev->vevent, ictime); +} + +static void +event_set_end(struct event *ev, time_t time, const icaltimezone *zone) { + if (zone == NULL) + zone = g_timezone; + icaltimetype ictime = icaltime_from_timet_with_zone(time, 1, zone); + icalcomponent_set_dtend(ev->vevent, ictime); +} + + + static void calendar_drop(struct cal *cal, double mx, double my) { struct event *ev = cal->target; if (ev) { - time_t len = ev->end - ev->start; - ev->start = ev->drag_time; - ev->end = ev->start + len; + icaltime_span span = icalcomponent_get_span(ev->vevent); + icaltimetype start = icalcomponent_get_dtstart(ev->vevent); + + time_t len = span.end - span.start; + // XXX: should dragging timezone be the local timezone? + // XXX: this will probably destroy the timezone, we don't want that + // TODO: convert timezone on drag? + + printf("%s drag-time %d start %d diff %d\n", + icalcomponent_get_summary(ev->vevent), + ev->drag_time, + span.start, + ev->drag_time - span.start); + + icaltimetype startt = + icaltime_from_timet(ev->drag_time, 0); + + icalcomponent_set_dtstart(ev->vevent, startt); + + icaltimetype endt = + icaltime_from_timet(ev->drag_time + len, 0); + + icalcomponent_set_dtend(ev->vevent, endt); } } static void event_click(struct event *event) { - printf("clicked %s\n", event->title); + printf("clicked %s\n", icalcomponent_get_summary(event->vevent)); } static int @@ -219,7 +312,8 @@ on_press(GtkWidget *widget, GdkEventButton *ev, gpointer user_data) { if (cal->target) { cal->target->dragx = 0.0; cal->target->dragy = 0.0; - cal->target->drag_time = cal->target->start; + cal->target->drag_time = + icaltime_as_timet(icalcomponent_get_dtstart(cal->target->vevent)); cal->target = NULL; } break; @@ -232,31 +326,35 @@ on_press(GtkWidget *widget, GdkEventButton *ev, gpointer user_data) { return 1; } -static int -event_any_flags(struct event *events, int flag, int nevents) { +static struct event* +event_any_flags(struct event *events, int nevents, int flag) { for (int i = 0; i < nevents; i++) { if ((events[i].flags & flag) != 0) - return 1; + return &events[i]; } - return 0; + return NULL; } static void calendar_print_state(struct cal *cal) { - printf("%s %s\r", + static int c = 0; + printf("%s %s %d\r", (cal->flags & CAL_DRAGGING) != 0 ? "D " : " ", - (cal->flags & CAL_MDOWN) != 0 ? "M " : " " + (cal->flags & CAL_MDOWN) != 0 ? "M " : " ", + c++ ); fflush(stdout); } static int on_motion(GtkWidget *widget, GdkEventMotion *ev, gpointer user_data) { - static int prev_hit = 0; + static struct event* prev_hit = NULL; - int hit = 0; + struct event *hit = NULL; int state_changed = 0; int dragging_event = 0; + double mx = ev->x; + double my = ev->y; struct extra_data *data = (struct extra_data*)user_data; struct cal *cal = data->cal; @@ -280,13 +378,15 @@ on_motion(GtkWidget *widget, GdkEventMotion *ev, gpointer user_data) { } } - update_events_flags (cal->events, cal->nevents, ev->x, ev->y); + events_update_flags (cal->events, cal->nevents, mx, my); hit = event_any_flags(cal->events, cal->nevents, EV_HIGHLIGHTED); gdk_window_set_cursor(gdkwin, hit ? cursor_pointer : cursor_default); state_changed = dragging_event || hit != prev_hit; + printf("%p %p\r", hit, prev_hit); + fflush(stdout); prev_hit = hit; if (state_changed) @@ -304,7 +404,8 @@ events_hit (struct event *events, int nevents, double mx, double my) { return NULL; } -static int event_hit (struct event *ev, double mx, double my) { +static int +event_hit (struct event *ev, double mx, double my) { return mx >= ev->x && mx <= (ev->x + ev->width) @@ -320,7 +421,7 @@ update_event_flags (struct event *ev, double mx, double my) { } static void -update_events_flags (struct event *events, int nevents, double mx, double my) { +events_update_flags (struct event *events, int nevents, double mx, double my) { for (int i = 0; i < nevents; ++i) { struct event *ev = &events[i]; update_event_flags (ev, mx, my); @@ -405,7 +506,9 @@ format_locale_time(char *buffer, int bsize, struct tm *tm) { static void -draw_hours (cairo_t *cr, int sy, int width, int height) { +draw_hours (cairo_t *cr, time_t start, time_t end, + double zoom, int sy, int width, int height) +{ double section_height = ((double)height) / 48.0; char buffer[32] = {0}; const double col = 0.4; @@ -443,18 +546,21 @@ draw_hours (cairo_t *cr, int sy, int width, int height) { } static time_t -location_to_time(time_t start, double loc) { - return (time_t)((double)start) + (loc * DAY_SECONDS); +location_to_time(time_t start, time_t end, double loc) { + return (time_t)((double)start) + (loc * (end - start)); } -static double time_to_location (time_t viewt, time_t time) { - return ((double)(time - viewt) / ((double)DAY_SECONDS)); +static double time_to_location (time_t start, time_t end, time_t time) { + return ((double)(time - start) / ((double)(end - start))); } static void -event_update (struct event *ev, time_t view, int sx, int sy, int width, int height) { - double sloc = time_to_location(view, ev->start); - double eloc = time_to_location(view, ev->end); +event_update (struct event *ev, time_t view_start, time_t view_end, + int sx, int sy, int width, int height) +{ + icaltime_span span = icalcomponent_get_span(ev->vevent); + double sloc = time_to_location(view_start, view_end, span.start); + double eloc = time_to_location(view_start, view_end, span.end); double dloc = eloc - sloc; double eheight = dloc * height; @@ -476,7 +582,7 @@ event_draw (cairo_t *cr, struct cal *cal, struct event *ev, int height) { double x = ev->x; double y = ev->y; - time_t st = ev->start; + time_t st = icalcomponent_get_span(ev->vevent).start; struct tm lt; union rgba c = { @@ -487,15 +593,16 @@ event_draw (cairo_t *cr, struct cal *cal, struct event *ev, int height) { c.a += 0.25; } + // grid logic if (cal->target == ev && ((cal->flags & CAL_DRAGGING) != 0)) { /* x += ev->dragx; */ y += ev->dragy; - st = location_to_time(cal->view, y/height); + st = location_to_time(cal->view_start, cal->view_end, y/height); lt = *localtime(&st); lt.tm_min = round(lt.tm_min / cal->minute_round) * cal->minute_round; lt.tm_sec = 0; // removes jitter st = mktime(&lt); - y = time_to_location(cal->view, st) * height; + y = time_to_location(cal->view_start, cal->view_end, st) * height; cal->target->drag_time = st; } @@ -505,14 +612,14 @@ event_draw (cairo_t *cr, struct cal *cal, struct event *ev, int height) { draw_rectangle(cr, ev->width, ev->height); cairo_fill(cr); cairo_move_to(cr, x, y); - draw_rectangle(cr, ev->width, ev->height); + cairo_rel_line_to(cr, ev->width, 0); cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a*2); cairo_stroke(cr); cairo_move_to(cr, x + EVPAD, y + EVPAD + TXTPAD); cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); lt = *localtime(&st); format_locale_time(bsmall, 32, &lt); - sprintf(buffer, "%s %s", bsmall, ev->title); + sprintf(buffer, "%s %s", bsmall, icalcomponent_get_summary(ev->vevent)); cairo_show_text(cr, buffer); } @@ -526,7 +633,8 @@ calendar_update (struct cal *cal, int width, int height) { for (i = 0; i < cal->nevents; ++i) { struct event *ev = &cal->events[i]; - event_update(ev, cal->view, g_lmargin, GAP, width, height); + event_update(ev, cal->view_start, cal->view_end, + g_lmargin, GAP, width, height); } } @@ -563,26 +671,13 @@ int main(int argc, char *argv[]) double text_col = 0.6; struct cal cal; - struct tm nowtm; - time_t today; - - /* event_create(&ev); */ - /* event_create(&ev2); */ - /* time(&ev.start); */ - /* ev.start -= 60 * 60 * 4; */ - /* ev.end = ev.start + (60 * 60); */ - /* ev.title = "Coding this"; */ - - /* time(&ev2.start); */ - /* ev2.start = ev.start + (60*60*2); */ - /* ev2.end = ev2.start + (60*30); */ - /* ev2.title = "After coding this"; */ - - struct event events[] = { ev, ev2 }; calendar_create(&cal); - calendar_load_ical(&cal, "/home/jb55/Downloads/mycalendar.ics"); + on_change_view(&cal); + + // TODO: get system timezone + g_timezone = icaltimezone_get_builtin_timezone("America/Vancouver"); g_text_color.r = text_col; g_text_color.g = text_col;