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, <); 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(<); 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(¤t); 847 current_tm.tm_min = 0; 848 current_tm.tm_sec = 0; 849 return mktime(¤t_tm); 850 } 851 852 static int get_minute(time_t current) 853 { 854 struct tm current_tm; 855 current_tm = *localtime(¤t); 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(¤t); 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(¤t_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(¤t_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 }