viscal

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

commit bd411e62764da49b932372ff1c6974dad087460e
parent a31899549bf17de899704727045913496adb42c5
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 10 Oct 2018 17:38:37 -0700

edit mode

Diffstat:
Mviscal.c | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 239 insertions(+), 33 deletions(-)

diff --git a/viscal.c b/viscal.c @@ -1,6 +1,7 @@ #include <cairo/cairo.h> #include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> #include <libical/ical.h> #include <assert.h> #include <time.h> @@ -11,6 +12,8 @@ #define length(array) (sizeof((array))/sizeof((array)[0])) +#define clamp(val, low, high) (val < low ? low : (val > high ? high : val)) + #define max(a,b) \ ({ __typeof__ (a) _a = (a); \ __typeof__ (b) _b = (b); \ @@ -21,6 +24,8 @@ __typeof__ (b) _b = (b); \ _a < _b ? _a : _b; }) +#define EDITBUF_MAX 32768 + // TODO: heap-realloc events array #define MAX_EVENTS 1024 @@ -44,6 +49,7 @@ enum cal_flags { CAL_MDOWN = 1 << 0 , CAL_DRAGGING = 1 << 1 , CAL_SPLIT = 1 << 2 + , CAL_EDITING = 1 << 3 }; union rgba { @@ -71,6 +77,10 @@ struct event { time_t drag_time; }; +// used for temporary storage when editing summaries, descriptions, etc +static char g_editbuf[EDITBUF_MAX] = {0}; +static int g_editbuf_pos = 0; + struct cal { GtkWidget *widget; struct ical calendars[128]; @@ -150,13 +160,17 @@ calendar_create(struct cal *cal) { cal->zoom = 2.0; } +static void warn(const char *msg) { + printf("WARN %s\n", msg); +} + static void set_current_calendar(struct cal *cal, struct ical *ical) { for (int i = 0; i < cal->ncalendars; i++) { if (&cal->calendars[i] == ical) cal->selected_calendar_ind = i; } -} +} static struct ical *current_calendar(struct cal *cal) { if (cal->ncalendars == 0) @@ -266,6 +280,49 @@ static void select_closest_to_now(struct cal *cal) } +static void set_edit_buffer(const char *src) +{ + char *dst = g_editbuf; + int n = 0; + int c = EDITBUF_MAX; + + while (c-- && (*dst++ = *src++)) + n++; + + g_editbuf_pos = n; +} + +static struct event *get_selected_event(struct cal *cal) +{ + if (cal->nevents == 0 || cal->selected_event_ind == -1) + return NULL; + return &cal->events[cal->selected_event_ind]; +} + + +static void edit_mode(struct cal *cal) +{ + // TODO: STATUS BAR for edit mode + + struct event *event = + get_selected_event(cal); + + // don't enter edit mode if we're not selecting any event + if (!event) + return; + + cal->flags |= CAL_EDITING; + + const char *summary = + icalcomponent_get_summary(event->vevent); + + // TODO: what are we editing? for now assume summary + // copy current summary to edit buffer + set_edit_buffer(summary); +} + + + static void events_for_view(struct cal *cal, time_t start, time_t end) { @@ -302,6 +359,8 @@ events_for_view(struct cal *cal, time_t start, time_t end) for (i = 0; i < cal->nevents; i++) { if (cal->events[i].vevent == cal->select_after_sort) { cal->selected_event_ind = i; + // HACK: we might not always want to do this... + edit_mode(cal); break; } } @@ -503,17 +562,14 @@ static char *format_locale_timet(char *buffer, int bsize, time_t time) { static icalcomponent * create_event(struct cal *cal, time_t start, time_t end, icalcomponent *ical) { - static char c = 'a'; - static char buf[128] = "New Event "; + static const char *default_event_summary = ""; icalcomponent *vevent; icaltimetype dtstart = icaltime_from_timet_with_zone(start, 0, NULL); icaltimetype dtend = icaltime_from_timet_with_zone(end, 0, NULL); vevent = icalcomponent_new(ICAL_VEVENT_COMPONENT); - buf[10] = c++; - buf[11] = 0; - icalcomponent_set_summary(vevent, buf); + icalcomponent_set_summary(vevent, default_event_summary); icalcomponent_set_dtstart(vevent, dtstart); icalcomponent_set_dtend(vevent, dtend); icalcomponent_add_component(ical, vevent); @@ -603,15 +659,6 @@ static void zoom(struct cal *cal, double amt) cal->zoom_at = cal->my; } -static struct event *get_selected_event(struct cal *cal) -{ - if (cal->nevents == 0 || cal->selected_event_ind == -1) - return NULL; - return &cal->events[cal->selected_event_ind]; -} - -#define clamp(val, low, high) (val < low ? low : (val > high ? high : val)) - static inline int relative_selection(struct cal *cal, int rel) { /* if (cal->selected_event_ind == -1) { */ @@ -639,6 +686,7 @@ static void move_now(struct cal *cal) closest_timeblock_for_timet(now, cal->timeblock_size); } + static void insert_event(struct cal *cal) { // we should eventually always have a calendar @@ -669,7 +717,7 @@ static int query_span(struct cal *cal, int index_hint, time_t start, time_t end, continue; vevent_span_timet(ev->vevent, &st, &et); - + if ((min_start != 0 && st < min_start) || (max_end != 0 && et > max_end)) continue; @@ -704,7 +752,7 @@ static void move_relative(struct cal *cal, int rel) st = cal->current; et = cal->current + timeblock; - + if ((hit = query_span(cal, 0, st, et, 0, 0)) != -1) { struct event *ev = &cal->events[hit]; vevent_span_timet(ev->vevent, &st, &et); @@ -818,6 +866,128 @@ static void open_below(struct cal *cal) } +static void finish_editing(struct cal *cal) +{ + struct event *event = get_selected_event(cal); + + if (!event) + return; + + // TODO: what are we editing? + // Right now we can only edit the summary + + // set summary of selected event + icalcomponent_set_summary(event->vevent, g_editbuf); + + // leave edit mode + cal->flags &= ~CAL_EDITING; +} + +static void append_str_edit_buffer(const char *src) +{ + if (*src == '\0') + return; + + char *dst = &g_editbuf[g_editbuf_pos]; + int c = g_editbuf_pos; + + while (c < EDITBUF_MAX && (*dst++ = *src++)) + c++; + + if (c == EDITBUF_MAX-1) + g_editbuf[EDITBUF_MAX-1] = '\0'; + + g_editbuf_pos = c; + +} + +/* static void append_edit_buffer(char key) */ +/* { */ +/* if (g_editbuf_pos + 1 >= EDITBUF_MAX) { */ +/* warn("attempting to write past end of edit buffer"); */ +/* return; */ +/* } */ +/* g_editbuf[g_editbuf_pos++] = key; */ +/* } */ + +static void pop_edit_buffer(int amount) +{ + amount = clamp(amount, 0, EDITBUF_MAX-1); + int top = g_editbuf_pos - amount; + top = clamp(top, 0, EDITBUF_MAX-1); + g_editbuf[top] = '\0'; + g_editbuf_pos = top; +} + +static void cancel_editing(struct cal *cal) +{ + cal->flags &= ~CAL_EDITING; +} + +static void pop_word_edit_buffer() +{ + int c = clamp(g_editbuf_pos - 2, 0, EDITBUF_MAX-1); + char *p = &g_editbuf[c]; + + while (p >= g_editbuf && *(p--) != ' ') + ; + + if (*(p + 1) == ' ') { + p += 2; + *p = '\0'; + } + else + *(++p) = '\0'; + + g_editbuf_pos = p - g_editbuf; + + return; +} + + +static int on_edit_keypress(struct cal *cal, GdkEventKey *event) +{ + char key = *event->string; + + switch (event->keyval) { + case GDK_KEY_Escape: + cancel_editing(cal); + return 1; + + case GDK_KEY_Return: + finish_editing(cal); + return 1; + + case GDK_KEY_BackSpace: + pop_edit_buffer(1); + return 1; + } + + if (key == 0x17) { + pop_word_edit_buffer(); + return 1; + } + + // TODO: more special edit keys + + if (*event->string >= 0x20) + append_str_edit_buffer(event->string); + + return 1; +} + +static void debug_edit_buffer(GdkEventKey *event) +{ + int len = strlen(event->string); + printf("edit buffer: %s[%x][%ld] %d %d '%s'\n", + event->string, + len > 0 ? *event->string : '\0', + strlen(event->string), + event->state, + g_editbuf_pos, + g_editbuf); +} + static gboolean on_keypress (GtkWidget *widget, GdkEvent *event, gpointer user_data) { struct extra_data *data = (struct extra_data*)user_data; @@ -830,7 +1000,14 @@ static gboolean on_keypress (GtkWidget *widget, GdkEvent *event, gpointer user_ switch (event->type) { case GDK_KEY_PRESS: + if (cal->flags & CAL_EDITING) { + state_changed = on_edit_keypress(cal, &event->key); + debug_edit_buffer(&event->key); + goto check_state; + } + key = *event->key.string; + int nkey = key - '0'; if (nkey >= 2 && nkey <= 9) { @@ -845,6 +1022,10 @@ static gboolean on_keypress (GtkWidget *widget, GdkEvent *event, gpointer user_ cal->repeat = 1; break; + case 'e': + edit_mode(cal); + break; + case 't': move_now(cal); break; @@ -919,6 +1100,7 @@ static gboolean on_keypress (GtkWidget *widget, GdkEvent *event, gpointer user_ break; } +check_state: if (state_changed) on_state_change(widget, event, user_data); @@ -1267,9 +1449,8 @@ static void saturate(union rgba *c, double change) c->b = P+((c->b)-P)*change; } - static void -draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { +draw_event (cairo_t *cr, struct cal *cal, struct event *ev, struct event *sel) { // double height = Math.fmin(, MIN_EVENT_HEIGHT); // stdout.printf("sloc %f eloc %f dloc %f eheight %f\n", // sloc, eloc, dloc, eheight); @@ -1286,6 +1467,9 @@ draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { /* icaltimetype dtend = icalcomponent_get_dtend(ev->vevent); */ int isdate = dtstart.is_date; + int is_selected = sel == ev; + int is_editing = is_selected && (cal->flags & CAL_EDITING); + time_t st, et; vevent_span_timet(ev->vevent, &st, &et); @@ -1301,8 +1485,10 @@ draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { time_t len = et - st; cairo_text_extents_t exts; + // TODO: we may not be editing the summary const char * const summary = - icalcomponent_get_summary(ev->vevent); + is_editing ? g_editbuf + : icalcomponent_get_summary(ev->vevent); if (is_dragging || ev->flags & EV_HIGHLIGHTED) { c.a *= 0.95; @@ -1322,9 +1508,8 @@ draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { cairo_move_to(cr, x, y); // TODO: selected event rendering - if (get_selected_event(cal) == ev) { + if (is_selected) saturate(&c, 0.5); - } cairo_set_source_rgba(cr, c.r, c.g, c.b, c.a); draw_rectangle(cr, ev->width, evheight); @@ -1333,7 +1518,7 @@ draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { static const double txtc = 0.2; cairo_set_source_rgb(cr, txtc, txtc, txtc); if (isdate) { - sprintf(buffer, "%s", summary); + sprintf(buffer, is_selected ? "'%s'" : "%s", summary); cairo_text_extents(cr, buffer, &exts); cairo_move_to(cr, x + EVPAD, y + (evheight / 2.0) + ((double)exts.height / 2.0)); @@ -1365,17 +1550,35 @@ draw_event (cairo_t *cr, struct cal *cal, struct event *ev) { format_time_duration(duration_format_in, sizeof(duration_format), in); format_time_duration(duration_format_out, sizeof(duration_format), out); - if (out >= 0 && in >= 0 && out < len) - sprintf(buffer, "%s | %s | %s in | %s left", summary, - duration_format, - duration_format_in, - duration_format_out); - else if (in >= 0 && in < 0) - sprintf(buffer, "%s | %s | %s in", summary, + if (out >= 0 && in >= 0 && out < len) { + const char *fmt = + is_editing + ? "'%s' | %s | %s in | %s left" + : "%s | %s | %s in | %s left"; + + sprintf(buffer, fmt, summary, + duration_format, + duration_format_in, + duration_format_out); + } + else if (in >= 0 && in < 0) { + const char *fmt = + is_editing + ? "'%s' | %s | %s in" + : "%s | %s | %s in"; + + sprintf(buffer, fmt, summary, duration_format, duration_format_in); - else - sprintf(buffer, "%s | %s", summary, duration_format); + } + else { + const char *fmt = + is_editing + ? "'%s' | %s" + : "%s | %s"; + + sprintf(buffer, fmt, summary, duration_format); + } cairo_text_extents(cr, buffer, &exts); double ey = evheight < exts.height @@ -1441,10 +1644,13 @@ draw_calendar (cairo_t *cr, struct cal *cal) { draw_background(cr, width, height); draw_hours(cr, cal); + struct event *selected = + get_selected_event(cal); + // draw calendar events for (i = 0; i < cal->nevents; ++i) { struct event *ev = &cal->events[i]; - draw_event(cr, cal, ev); + draw_event(cr, cal, ev, selected); } if (cal->selected_event_ind == -1)