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