/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* gcal-year-view.c * * Copyright (C) 2015 Erick Pérez Castellanos * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "gcal-year-view.h" #include "gcal-view.h" #include "gcal-utils.h" #include #include #include #define NAVIGATOR_MAX_GRID_SIZE 4 #define VISUAL_CLUES_SIDE 3.0 #define WEEK_NUMBER_MARGIN 3.0 typedef struct { /* month span from 0 to 11 */ gint start_day, start_month; gint end_day, end_month; gint hovered_day, hovered_month; gint dnd_day, dnd_month; } ButtonData; typedef struct { gdouble box_side; GdkPoint coordinates [12]; } GridData; struct _GcalYearView { GtkBox parent; /* composite, GtkBuilder's widgets */ GtkWidget *navigator; GtkWidget *sidebar; GtkWidget *events_sidebar; GtkWidget *navigator_stack; GtkWidget *no_events_title; GtkWidget *navigator_sidebar; GtkWidget *popover; /* Popover for popover_mode */ /* manager singleton */ GcalManager *manager; /* range shown on the sidebar */ icaltimetype *start_selected_date; icaltimetype *end_selected_date; /* geometry info */ GridData *navigator_grid; guint number_of_columns; guint column_width; guint row_height; guint header_height; guint sidebar_width; /* state flags */ gboolean popover_mode; gboolean button_pressed; ButtonData *selected_data; /** * first day of the week according to user locale, being * 0 for Sunday, 1 for Monday and so on */ gint first_weekday; /* first and last weeks of the year */ guint first_week_of_year; guint last_week_of_year; /** * clock format from GNOME desktop settings */ gboolean use_24h_format; /* show week numbers from GNOME Shell settings */ GSettings *shell_settings; gboolean show_week_numbers; /* text direction factors */ gint k; /* weak ref to current date */ icaltimetype *current_date; /* date property */ icaltimetype *date; }; enum { PROP_0, PROP_DATE, PROP_SHOW_WEEK_NUMBERS, LAST_PROP }; enum { EVENT_ACTIVATED, NUM_SIGNALS }; static guint signals[NUM_SIGNALS] = { 0, }; static void reset_sidebar (GcalYearView *year_view); static void gcal_view_interface_init (GcalViewInterface *iface); static void gcal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface); G_DEFINE_TYPE_WITH_CODE (GcalYearView, gcal_year_view, GTK_TYPE_BOX, G_IMPLEMENT_INTERFACE (GCAL_TYPE_VIEW, gcal_view_interface_init) G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, gcal_data_model_subscriber_interface_init)); static gint compare_events (GcalEventWidget *widget1, GcalEventWidget *widget2) { return gcal_event_compare (gcal_event_widget_get_event (widget1), gcal_event_widget_get_event (widget2)); } static guint get_last_week_of_year_dmy (gint first_weekday, GDateDay day, GDateMonth month, GDateYear year) { GDate day_of_year; gint day_of_week; g_date_set_dmy (&day_of_year, day, month, year); day_of_week = g_date_get_weekday (&day_of_year) % 7; if (day_of_week >= first_weekday) g_date_add_days (&day_of_year, (6 - day_of_week) + first_weekday); else g_date_add_days (&day_of_year, first_weekday - day_of_week - 1); return g_date_get_iso8601_week_of_year (&day_of_year); } static void update_date (GcalYearView *year_view, icaltimetype *new_date) { if (year_view->date != NULL && icaltime_compare_date (year_view->date, new_date) && year_view->start_selected_date->day != 0) reset_sidebar (year_view); g_clear_pointer (&year_view->date, g_free); year_view->date = new_date; year_view->first_week_of_year = get_last_week_of_year_dmy (year_view->first_weekday, 1, G_DATE_JANUARY, year_view->date->year);; year_view->last_week_of_year = get_last_week_of_year_dmy (year_view->first_weekday, 31, G_DATE_DECEMBER, year_view->date->year); } static void event_activated (GcalEventWidget *widget, gpointer user_data) { GcalYearView *view = GCAL_YEAR_VIEW (user_data); if (view->popover_mode) gtk_widget_hide (view->popover); g_signal_emit (GCAL_YEAR_VIEW (user_data), signals[EVENT_ACTIVATED], 0, widget); } static void order_selected_data (ButtonData *selected_data) { gint swap; if (selected_data->end_month < selected_data->start_month) { swap = selected_data->start_month; selected_data->start_month = selected_data->end_month; selected_data->end_month = swap; swap = selected_data->start_day; selected_data->start_day = selected_data->end_day; selected_data->end_day = swap; } else if (selected_data->start_month == selected_data->end_month && selected_data->end_day < selected_data->start_day) { swap = selected_data->start_day; selected_data->start_day = selected_data->end_day; selected_data->end_day = swap; } } static void update_selected_dates_from_button_data (GcalYearView *year_view) { if (year_view->selected_data->start_day != 0) { ButtonData selected_data = *(year_view->selected_data); order_selected_data (&selected_data); year_view->start_selected_date->day = selected_data.start_day; year_view->start_selected_date->month = selected_data.start_month + 1; year_view->start_selected_date->year = year_view->date->year; year_view->end_selected_date->is_date = TRUE; year_view->end_selected_date->day = selected_data.end_day; year_view->end_selected_date->month = selected_data.end_month + 1; year_view->end_selected_date->year = year_view->date->year; year_view->end_selected_date->hour = 23; year_view->end_selected_date->minute = 59; year_view->end_selected_date->is_date = TRUE; *(year_view->end_selected_date) = icaltime_normalize (*(year_view->end_selected_date)); } else { *(year_view->start_selected_date) = *(year_view->current_date); year_view->start_selected_date->hour = 0; year_view->start_selected_date->minute = 0; year_view->start_selected_date->second = 0; *(year_view->end_selected_date) = *(year_view->current_date); year_view->end_selected_date->hour = 23; year_view->end_selected_date->minute = 59; *(year_view->end_selected_date) = icaltime_normalize (*(year_view->end_selected_date)); } if (year_view->end_selected_date->year != year_view->start_selected_date->year) { year_view->end_selected_date->day = 31; year_view->end_selected_date->month = 12; year_view->end_selected_date->year = year_view->start_selected_date->year; } } static void update_no_events_page (GcalYearView *year_view) { gchar *title; gboolean has_range; has_range = (year_view->start_selected_date->day != year_view->end_selected_date->day || year_view->start_selected_date->month != year_view->end_selected_date->month); if (icaltime_compare_date (year_view->current_date, year_view->start_selected_date) == 0) { title = g_strdup_printf ("%s%s", _("Today"), has_range ? "…" : ""); } else { title = g_strdup_printf ("%s %d%s", gcal_get_month_name (year_view->start_selected_date->month - 1), year_view->start_selected_date->day, has_range ? "…" : ""); } gtk_label_set_text (GTK_LABEL (year_view->no_events_title), title); g_free (title); } static void add_event_to_day_array (GcalYearView *year_view, GcalEvent *event, GList **days_widgets_array, gint days_span) { GtkWidget *child_widget; GDateTime *dt_start, *dt_end; GDateTime *date, *second_date; gint i; child_widget = gcal_event_widget_new (event); gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (child_widget), !gcal_manager_is_client_writable (year_view->manager, gcal_event_get_source (event))); dt_start = gcal_event_get_date_start (event); dt_end = gcal_event_get_date_end (event); /* normalize date on each new event */ date = icaltime_to_datetime (year_view->start_selected_date); second_date = g_date_time_add_days (date, 1); /* marking and cloning */ for (i = 0; i < days_span; i++) { GtkWidget *cloned_child; GDateTime *aux; gint start_comparison; cloned_child = child_widget; start_comparison = datetime_compare_date (dt_start, date); if (start_comparison > 0) { /* * The start date of the event is ahead of the current * date being comparent, so we don't add it now. */ cloned_child = NULL; } else if (start_comparison == 0) { /* * This is the first day the event happens, so we just use * the previously created event widget. */ cloned_child = child_widget; } else { /* * We're in the middle of the event and the dates are between * the event start date > current date > event end date, so * we keep cloning the event widget until we reach the end of * the event. */ cloned_child = gcal_event_widget_clone (GCAL_EVENT_WIDGET (child_widget)); } if (cloned_child != NULL) { gint end_comparison; /* * Setup the event widget's custom dates, so the slanted edges are * applied properly */ if (gcal_event_is_multiday (event)) { gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (cloned_child), date); gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (cloned_child), second_date); } days_widgets_array[i] = g_list_insert_sorted (days_widgets_array[i], cloned_child, (GCompareFunc) compare_events); end_comparison = datetime_compare_date (second_date, dt_end); /* * If the end date surpassed the event's end date, we reached the * end of the event's time span and shall stop adding the event to * the list. */ if (end_comparison >= 0) break; } /* Move the compared dates to the next day */ aux = date; date = g_date_time_add_days (date, 1); g_clear_pointer (&aux, g_date_time_unref); aux = second_date; second_date = g_date_time_add_days (second_date, 1); g_clear_pointer (&aux, g_date_time_unref); } g_clear_pointer (&second_date, g_date_time_unref); g_clear_pointer (&date, g_date_time_unref); } static void update_sidebar (GcalYearView *year_view) { GtkWidget *child_widget; GList *events, *l; GList **days_widgets_array; gint i, days_span; update_selected_dates_from_button_data (year_view); gtk_container_foreach (GTK_CONTAINER (year_view->events_sidebar), (GtkCallback) gtk_widget_destroy, NULL); days_span = icaltime_day_of_year(*(year_view->end_selected_date)) - icaltime_day_of_year(*(year_view->start_selected_date)) + 1; days_widgets_array = g_new0 (GList*, days_span); events = gcal_manager_get_events (year_view->manager, year_view->start_selected_date, year_view->end_selected_date); if (events == NULL) { days_span = 0; update_no_events_page (year_view); gtk_stack_set_visible_child_name (GTK_STACK (year_view->navigator_stack), "no-events"); } else { gtk_stack_set_visible_child_name (GTK_STACK (year_view->navigator_stack), "events-list"); } for (l = events; l != NULL; l = g_list_next (l)) add_event_to_day_array (year_view, l->data, days_widgets_array, days_span); gtk_widget_queue_draw (year_view->navigator); for (i = 0; i < days_span; i++) { GList *current_day = days_widgets_array[i]; for (l = current_day; l != NULL; l = g_list_next (l)) { child_widget = l->data; gtk_widget_show (child_widget); g_signal_connect (child_widget, "activate", G_CALLBACK (event_activated), year_view); g_object_set_data (G_OBJECT (child_widget), "shift", GINT_TO_POINTER (i)); gtk_container_add (GTK_CONTAINER (year_view->events_sidebar), child_widget); } g_list_free (current_day); } g_list_free_full (events, g_object_unref); g_free (days_widgets_array); } static void reset_sidebar (GcalYearView *year_view) { memset (year_view->selected_data, 0, sizeof (ButtonData)); gtk_widget_queue_draw (GTK_WIDGET (year_view)); update_sidebar (year_view); } static void update_sidebar_headers (GtkListBoxRow *row, GtkListBoxRow *before, gpointer user_data) { GcalYearView *year_view; GtkWidget *row_child, *before_child = NULL, *row_header = NULL; GcalEvent *row_event, *before_event; GDateTime *row_date, *before_date = NULL; icaltimetype date; gint row_shift, before_shift =-1; year_view = GCAL_YEAR_VIEW (user_data); row_child = gtk_bin_get_child (GTK_BIN (row)); if (row_child == NULL) return; row_event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (row_child)); row_date = gcal_event_get_date_start (row_event); row_date = g_date_time_to_local (row_date); row_shift = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row_child), "shift")); if (before != NULL) { before_child = gtk_bin_get_child (GTK_BIN (before)); before_event = gcal_event_widget_get_event (GCAL_EVENT_WIDGET (before_child)); before_date = gcal_event_get_date_start (before_event); before_date = g_date_time_to_local (before_date); before_shift = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (before_child), "shift")); } if (before_shift == -1 || before_shift != row_shift) { GtkWidget *label; gchar *label_str; row_header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); date = *(year_view->start_selected_date); icaltime_adjust (&date, row_shift, 0, 0, 0); if (icaltime_compare_date (&date, year_view->current_date) == 0) label_str = g_strdup (_("Today")); else label_str = g_strdup_printf ("%s %d", gcal_get_month_name (date.month - 1), date.day); label = gtk_label_new (label_str); gtk_style_context_add_class (gtk_widget_get_style_context (label), "sidebar-header"); g_object_set (label, "margin", 6, "halign", GTK_ALIGN_START, NULL); g_free (label_str); gtk_container_add (GTK_CONTAINER (row_header), label); } if (!gcal_event_is_multiday (row_event) && !gcal_event_get_all_day (row_event) && (before_date == NULL || g_date_time_get_hour (before_date) != g_date_time_get_hour (row_date))) { gchar *time; GtkWidget *label; gint hour; hour = g_date_time_get_hour (row_date); if (year_view->use_24h_format) time = g_strdup_printf ("%.2d:00", hour); else time = g_strdup_printf ("%.2d:00 %s", hour % 12, hour < 12 ? "AM" : "PM"); label = gtk_label_new (time); gtk_style_context_add_class (gtk_widget_get_style_context (label), GTK_STYLE_CLASS_DIM_LABEL); g_object_set (label, "margin", 6, "halign", GTK_ALIGN_START, NULL); if (row_header == NULL) row_header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_container_add (GTK_CONTAINER (row_header), label); gtk_container_add (GTK_CONTAINER (row_header), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL)); g_free (time); } if (row_header != NULL) gtk_widget_show_all (row_header); gtk_list_box_row_set_header (row, row_header); g_clear_pointer (&before_date, g_date_time_unref); g_clear_pointer (&row_date, g_date_time_unref); } static gint sidebar_sort_func (GtkListBoxRow *row1, GtkListBoxRow *row2, gpointer user_data) { GtkWidget *row1_child, *row2_child; gint result, row1_shift, row2_shift; row1_child = gtk_bin_get_child (GTK_BIN (row1)); row2_child = gtk_bin_get_child (GTK_BIN (row2)); row1_shift = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row1_child), "shift")); row2_shift = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row2_child), "shift")); result = row1_shift - row2_shift; if (result == 0) return compare_events (GCAL_EVENT_WIDGET (row1_child), GCAL_EVENT_WIDGET (row2_child)); return result; } static gboolean calculate_day_month_for_coord (GcalYearView *year_view, gdouble x, gdouble y, gint *out_day, gint *out_month, gboolean *is_title) { gint row, column, i, sw, clicked_cell, day, month, columns_or_rows; gint number_of_rows; gdouble box_side; row = -1; column = -1; box_side = year_view->navigator_grid->box_side; number_of_rows = ceil (12.0 / year_view->number_of_columns); sw = 1 - 2 * year_view->k; *is_title = FALSE; columns_or_rows = year_view->number_of_columns > number_of_rows ? year_view->number_of_columns : number_of_rows; for (i = 0; i < columns_or_rows && ((row == -1) || (column == -1)); i++) { if (row == -1 && y > year_view->navigator_grid->coordinates[i * year_view->number_of_columns].y && y < year_view->navigator_grid->coordinates[i * year_view->number_of_columns].y + year_view->row_height) { if (y < year_view->navigator_grid->coordinates[i * year_view->number_of_columns].y + box_side) *is_title = TRUE; row = i; } if (column == -1 && x > year_view->navigator_grid->coordinates[i].x + box_side * year_view->show_week_numbers * (1 - year_view->k) && x < year_view->navigator_grid->coordinates[i].x + year_view->column_width - box_side * year_view->show_week_numbers * year_view->k) { column = i; } } if (row == -1 || column == -1) return FALSE; month = row * year_view->number_of_columns + column; if (month < 0 || month > 11) return FALSE; *out_month = month; if (*is_title) return TRUE; row = (y - (year_view->navigator_grid->coordinates[month].y + box_side)) / box_side; column = (x - (year_view->navigator_grid->coordinates[month].x + box_side * year_view->show_week_numbers * (1 - year_view->k))) / box_side; clicked_cell = row * 7 + column; day = 7 * ((clicked_cell + 7 * year_view->k) / 7) + sw * (clicked_cell % 7) + (1 - year_view->k); day -= ((time_day_of_week (1, month, year_view->date->year) - year_view->first_weekday + 7) % 7); if (day < 1 || day > time_days_in_month (year_view->date->year, month)) return FALSE; *out_day = day; return TRUE; } static void draw_month_grid (GcalYearView *year_view, GtkWidget *widget, cairo_t *cr, gint month_nr, gint *weeks_counter) { GtkStyleContext *context; GtkStateFlags state_flags; PangoLayout *layout, *slayout; PangoFontDescription *font_desc, *sfont_desc; GdkRGBA color; gint layout_width, layout_height, i, j, sw; gint column, row; gdouble x, y, box_side, box_padding_top, box_padding_start; gint days_delay, days, shown_rows, sunday_idx; gchar *str, *nr_day, *nr_week; gboolean selected_day; GList *events; icaltimetype start_date, end_date; cairo_save (cr); context = gtk_widget_get_style_context (widget); state_flags = gtk_style_context_get_state (context); sw = 1 - 2 * year_view->k; box_side = year_view->navigator_grid->box_side; x = year_view->navigator_grid->coordinates[month_nr].x; y = year_view->navigator_grid->coordinates[month_nr].y; /* Get the font description */ gtk_style_context_save (context); gtk_style_context_set_state (context, state_flags | GTK_STATE_FLAG_SELECTED); gtk_style_context_get (context, state_flags | GTK_STATE_FLAG_SELECTED, "font", &sfont_desc, NULL); gtk_style_context_restore (context); slayout = gtk_widget_create_pango_layout (widget, NULL); pango_layout_set_font_description (slayout, sfont_desc); /* header */ gtk_style_context_save (context); gtk_style_context_add_class (context, "header"); str = g_strdup (gcal_get_month_name (month_nr)); gtk_style_context_get (context, state_flags, "font", &font_desc, NULL); layout = gtk_widget_create_pango_layout (widget, str); pango_layout_set_font_description (layout, font_desc); pango_layout_get_pixel_size (layout, &layout_width, &layout_height); gtk_render_layout (context, cr, x + (year_view->column_width - layout_width) / 2.0, y + (box_side - layout_height) / 2.0, layout); gtk_render_background (context, cr, x + (year_view->column_width - layout_width) / 2.0, y + (box_side - layout_height) / 2.0, layout_width, layout_width); pango_font_description_free (font_desc); g_free (str); gtk_style_context_restore (context); /* separator line */ gtk_style_context_save (context); gtk_style_context_add_class (context, "lines"); gtk_style_context_get_color (context, state_flags, &color); cairo_set_line_width (cr, 0.2); gdk_cairo_set_source_rgba (cr, &color); cairo_move_to (cr, x + box_side * year_view->show_week_numbers * (1 - year_view->k), y + box_side + 0.4); cairo_rel_line_to (cr, 7.0 * box_side, 0); cairo_stroke (cr); gtk_style_context_restore (context); /* days */ gtk_style_context_save (context); gtk_style_context_add_class (context, "days"); gtk_style_context_get (context, state_flags, "font", &font_desc, NULL); pango_layout_set_font_description (layout, font_desc); days_delay = (time_day_of_week (1, month_nr, year_view->date->year) - year_view->first_weekday + 7) % 7; days = days_delay + icaltime_days_in_month (month_nr + 1, year_view->date->year); shown_rows = ceil (days / 7.0); sunday_idx = year_view->k * 7 + sw * ((7 - year_view->first_weekday) % 7) + year_view->show_week_numbers * (1 - year_view->k); start_date.day = 1; start_date.month = month_nr + 1; start_date.year = year_view->date->year; start_date.hour = 0; start_date.minute = 0; start_date.second = 0; end_date.day = 1; end_date.month = month_nr + 1; end_date.year = year_view->date->year; end_date.hour = 23; end_date.minute = 59; end_date.second = 59; for (i = 0; i < 7 * shown_rows; i++) { column = (i % 7) + (year_view->k || year_view->show_week_numbers); row = i / 7; j = 7 * ((i + 7 * year_view->k) / 7) + sw * (i % 7) + (1 - year_view->k); if (j <= days_delay) continue; else if (j > days) continue; j -= days_delay; nr_day = g_strdup_printf ("%d", j); pango_layout_set_text (layout, nr_day, -1); pango_layout_get_pixel_size (layout, &layout_width, &layout_height); box_padding_top = (box_side - layout_height) / 2 > 0 ? (box_side - layout_height) / 2 : 0; box_padding_start = (box_side - layout_width) / 2 > 0 ? (box_side - layout_width) / 2 : 0; /* Draw the hover background */ if (year_view->selected_data->hovered_day == j && year_view->selected_data->hovered_month == month_nr) { gint hover_x, hover_y; hover_x = box_side * column + x + sw * box_padding_start / 2.0 - year_view->k * box_side - 2.0; hover_y = box_side * (row + 1) + y + box_padding_top - 1.0; gtk_style_context_set_state (context, state_flags | GTK_STATE_FLAG_PRELIGHT); gtk_render_background (context, cr, hover_x, hover_y, box_side - box_padding_start / 2.0, box_side); gtk_style_context_set_state (context, state_flags); } /* Draw the Drag n' Drop indicator */ if (year_view->selected_data->dnd_day == j && year_view->selected_data->dnd_month == month_nr) { gint dnd_x, dnd_y; dnd_x = box_side * column + x + sw * box_padding_start / 2.0 - year_view->k * box_side - 2.0; dnd_y = box_side * (row + 1) + y + box_padding_top - 1.0; gtk_style_context_save (context); gtk_style_context_add_class (context, "dnd"); gtk_render_background (context, cr, dnd_x, dnd_y, box_side - box_padding_start / 2.0, box_side); gtk_style_context_restore (context); } selected_day = FALSE; if (year_view->selected_data->start_day != 0) { ButtonData selected_data = *(year_view->selected_data); order_selected_data (&selected_data); if (month_nr > selected_data.start_month && month_nr < selected_data.end_month) { selected_day = TRUE; } else if (month_nr == selected_data.start_month && month_nr == selected_data.end_month) { selected_day = j >= selected_data.start_day && j <= selected_data.end_day; } else if (month_nr == selected_data.start_month && j >= selected_data.start_day) { selected_day = TRUE; } else if (month_nr == selected_data.end_month && j <= selected_data.end_day) { selected_day = TRUE; } } if (year_view->date->year == year_view->current_date->year && month_nr + 1 == year_view->current_date->month && j == year_view->current_date->day) { PangoLayout *clayout; PangoFontDescription *cfont_desc; gtk_style_context_save (context); gtk_style_context_add_class (context, "current"); clayout = gtk_widget_create_pango_layout (widget, nr_day); gtk_style_context_get (context, state_flags, "font", &cfont_desc, NULL); pango_layout_set_font_description (clayout, cfont_desc); pango_layout_get_pixel_size (clayout, &layout_width, &layout_height); box_padding_top = (box_side - layout_height) / 2 > 0 ? (box_side - layout_height) / 2 : 0; box_padding_start = (box_side - layout_width) / 2 > 0 ? (box_side - layout_width) / 2 : 0; /* FIXME: hardcoded padding of the number background */ gtk_render_background (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * layout_width - 2.0, box_side * (row + 1) + y + box_padding_top - 1.0, layout_width + 4.0, layout_height + 2.0); gtk_render_layout (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * layout_width, box_side * (row + 1) + y + box_padding_top, clayout); gtk_style_context_restore (context); pango_font_description_free (cfont_desc); g_object_unref (clayout); } else if (selected_day) { gtk_style_context_set_state (context, state_flags | GTK_STATE_FLAG_SELECTED); pango_layout_set_text (slayout, nr_day, -1); pango_layout_get_pixel_size (slayout, &layout_width, &layout_height); box_padding_top = (box_side - layout_height) / 2 > 0 ? (box_side - layout_height) / 2 : 0; box_padding_start = (box_side - layout_width) / 2 > 0 ? (box_side - layout_width) / 2 : 0; gtk_render_layout (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * layout_width, box_side * (row + 1) + y + box_padding_top, slayout); gtk_style_context_set_state (context, state_flags); } else if (column == sunday_idx) { gtk_style_context_save (context); gtk_style_context_add_class (context, "sunday"); gtk_render_layout (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * layout_width, box_side * (row + 1) + y + box_padding_top, layout); gtk_style_context_restore (context); } else { gtk_render_layout (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * layout_width, box_side * (row + 1) + y + box_padding_top, layout); } start_date.day = end_date.day = j; events = gcal_manager_get_events (year_view->manager, &start_date, &end_date); if (events != NULL) { gtk_style_context_save (context); gtk_style_context_add_class (context, "with-events"); if (selected_day) gtk_style_context_add_class (context, "with-events-selected"); else if (column == sunday_idx) gtk_style_context_add_class (context, "with-events-sunday"); box_padding_start = (box_side - VISUAL_CLUES_SIDE) / 2 > 0 ? (box_side - VISUAL_CLUES_SIDE) / 2 : 0; gtk_render_background (context, cr, box_side * column + x + sw * box_padding_start - year_view->k * VISUAL_CLUES_SIDE, box_side * (row + 1) + y + box_padding_top + layout_height + 2.0, VISUAL_CLUES_SIDE, VISUAL_CLUES_SIDE); gtk_style_context_restore (context); g_list_free_full (events, g_object_unref); } g_free (nr_day); } pango_font_description_free (font_desc); gtk_style_context_restore (context); /* week numbers */ gtk_style_context_save (context); gtk_style_context_add_class (context, "week-numbers"); gtk_style_context_get (context, state_flags, "font", &font_desc, NULL); pango_layout_set_font_description (layout, font_desc); for (i = 0; i < shown_rows; i++) { /* special condition for first and last weeks of the year */ if (month_nr == 0 && year_view->first_week_of_year > 1 && i == 1) *weeks_counter = 1; else if (month_nr == 11 && (i + 1) == shown_rows) *weeks_counter = year_view->last_week_of_year; /* other weeks */ else if (i == 0) { if (days_delay == 0 && month_nr != 0) *weeks_counter = *weeks_counter + 1; } else *weeks_counter = *weeks_counter + 1; if (year_view->show_week_numbers) { nr_week = g_strdup_printf ("%d", *weeks_counter); pango_layout_set_text (layout, nr_week, -1); pango_layout_get_pixel_size (layout, &layout_width, &layout_height); box_padding_top = (box_side - layout_height) / 2.0 > 0 ? (box_side - layout_height) / 2.0 : 0; box_padding_start = (box_side - layout_width) / 2.0 > 0 ? (box_side - layout_width) / 2.0 : 0; gtk_render_background (context, cr, x + sw * box_padding_top + year_view->k * (8 * box_side - layout_height) - WEEK_NUMBER_MARGIN, box_side * (i + 1) + y + box_padding_top - WEEK_NUMBER_MARGIN, layout_height + WEEK_NUMBER_MARGIN * 2, layout_height + WEEK_NUMBER_MARGIN * 2); gtk_render_layout (context, cr, x + sw * box_padding_start + year_view->k * (8 * box_side - layout_width), box_side * (i + 1) + y + box_padding_top, layout); g_free (nr_week); } } gtk_style_context_restore (context); pango_font_description_free (sfont_desc); g_object_unref (slayout); pango_font_description_free (font_desc); g_object_unref (layout); cairo_restore (cr); } static gboolean draw_navigator (GcalYearView *year_view, cairo_t *cr, GtkWidget *widget) { GtkStyleContext *context; GtkStateFlags state_flags; gint header_padding_left, header_padding_top, header_height, layout_width, layout_height; gint real_padding_left, real_padding_top, i, sw, weeks_counter, number_of_rows; gdouble width, height; gchar *header_str; PangoLayout *header_layout; PangoFontDescription *font_desc; context = gtk_widget_get_style_context (GTK_WIDGET (year_view)); state_flags = gtk_style_context_get_state (context); sw = 1 - 2 * year_view->k; width = gtk_widget_get_allocated_width (widget); number_of_rows = ceil (12.0 / year_view->number_of_columns); /* read header from CSS code related to the view */ gtk_style_context_save (context); gtk_style_context_add_class (context, "first-view-header"); header_str = g_strdup_printf ("%d", year_view->date->year); gtk_style_context_get (context, state_flags, "padding-left", &header_padding_left, "padding-top", &header_padding_top, "font", &font_desc, NULL); gtk_style_context_restore (context); /* draw header on navigator */ context = gtk_widget_get_style_context (widget); state_flags = gtk_style_context_get_state (context); header_layout = gtk_widget_create_pango_layout (widget, header_str); pango_layout_set_font_description (header_layout, font_desc); pango_layout_get_pixel_size (header_layout, &layout_width, &layout_height); /* XXX: here the color of the text isn't read from year-view but from navigator widget, * which has the same color on the CSS file */ gtk_render_layout (context, cr, year_view->k * (width - layout_width) + sw * header_padding_left, header_padding_top, header_layout); pango_font_description_free (font_desc); g_object_unref (header_layout); g_free (header_str); header_height = header_padding_top * 2 + layout_height; height = gtk_widget_get_allocated_height (widget) - header_height; real_padding_left = (width - year_view->column_width * year_view->number_of_columns) / (year_view->number_of_columns + 1); real_padding_top = (height - year_view->row_height * number_of_rows) / number_of_rows; if (real_padding_top < 0) real_padding_top = 0; weeks_counter = year_view->first_week_of_year; for (i = 0; i < 12; i++) { gint row = i / year_view->number_of_columns; gint column = year_view->k * (year_view->number_of_columns - 1) + sw * (i % year_view->number_of_columns); year_view->navigator_grid->coordinates[i].x = (column + 1) * real_padding_left + column * year_view->column_width; year_view->navigator_grid->coordinates[i].y = row * real_padding_top + row * year_view->row_height + header_height; draw_month_grid (year_view, widget, cr, i, &weeks_counter); } return FALSE; } static gboolean navigator_button_press_cb (GcalYearView *year_view, GdkEventButton *event, GtkWidget *widget) { gint day, month; gboolean is_title = FALSE; if (!calculate_day_month_for_coord (year_view, event->x, event->y, &day, &month, &is_title)) return FALSE; if (is_title) day = 1; year_view->button_pressed = TRUE; year_view->selected_data->start_day = day; year_view->selected_data->start_month = month; return TRUE; } static gboolean navigator_button_release_cb (GcalYearView *year_view, GdkEventButton *event, GtkWidget *widget) { gint day, month; gboolean is_title = FALSE; if (!year_view->button_pressed) return FALSE; if (!calculate_day_month_for_coord (year_view, event->x, event->y, &day, &month, &is_title)) goto fail; if (is_title) day = g_date_get_days_in_month (month + 1, year_view->date->year); year_view->button_pressed = FALSE; year_view->selected_data->end_day = day; year_view->selected_data->end_month = month; /* update date and notify */ year_view->date->day = day; year_view->date->month = month + 1; g_object_notify (G_OBJECT (year_view), "active-date"); gtk_widget_queue_draw (widget); if (year_view->popover_mode) { GdkRectangle rect; GtkWidget *box; /* sizing */ box = gtk_bin_get_child (GTK_BIN (year_view->popover)); gtk_widget_set_size_request (box, 200, year_view->navigator_grid->box_side * 2 * 7); /* FIXME: improve rect */ rect.x = event->x; rect.y = event->y; rect.width = rect.height = 1; gtk_popover_set_pointing_to (GTK_POPOVER (year_view->popover), &rect); /* FIXME: do no show over selected days */ gtk_popover_set_position (GTK_POPOVER (year_view->popover), GTK_POS_RIGHT); gtk_widget_show (year_view->popover); } update_sidebar (year_view); return TRUE; fail: year_view->button_pressed = FALSE; reset_sidebar (year_view); return TRUE; } static gboolean navigator_motion_notify_cb (GcalYearView *year_view, GdkEventMotion *event, GtkWidget *widget) { gint day, month; gboolean is_title = FALSE; /* Cancel the hover when selecting a date range */ year_view->selected_data->hovered_day = -1; year_view->selected_data->hovered_month = -1; if (calculate_day_month_for_coord (year_view, event->x, event->y, &day, &month, &is_title)) { if (year_view->button_pressed) { if (is_title) day = g_date_get_days_in_month (month + 1, year_view->date->year); year_view->selected_data->end_day = day; year_view->selected_data->end_month = month; gtk_widget_queue_draw (widget); return GDK_EVENT_STOP; } else { /* We only deal with the hover effect when the button is not pressed */ year_view->selected_data->hovered_day = day; year_view->selected_data->hovered_month = month; gtk_widget_queue_draw (widget); return GDK_EVENT_PROPAGATE; } } gtk_widget_queue_draw (widget); return GDK_EVENT_STOP; } static void add_event_clicked_cb (GcalYearView *year_view, GtkButton *button) { GDateTime *start_date, *end_date = NULL; if (year_view->start_selected_date->day == 0) { start_date = g_date_time_new_local (year_view->current_date->year, year_view->current_date->month, year_view->current_date->day, 0, 0, 0); } else { icaltimetype *dtstart, *dtend; GDateTime *tmp_date; dtstart = year_view->start_selected_date; dtend = year_view->end_selected_date; start_date = g_date_time_new_local (dtstart->year, dtstart->month, dtstart->day, 0, 0, 0); tmp_date = g_date_time_new_local (dtend->year, dtend->month, dtend->day, 0, 0, 0); end_date = g_date_time_add_days (tmp_date, 1); g_clear_pointer (&tmp_date, g_date_time_unref); } if (year_view->popover_mode) gtk_widget_hide (year_view->popover); g_signal_emit_by_name (GCAL_VIEW (year_view), "create-event-detailed", start_date, end_date); g_clear_pointer (&start_date, g_date_time_unref); g_clear_pointer (&end_date, g_date_time_unref); } static void popover_closed_cb (GcalYearView *year_view, GtkPopover *popover) { reset_sidebar (year_view); } static void calculate_grid_sizes (GcalYearView *self) { /* 1 column for week number and 7 for days */ if (self->show_week_numbers) self->column_width = self->navigator_grid->box_side * 8; else self->column_width = self->navigator_grid->box_side * 7; /* 1 line for month header and 6 lines for weeks */ self->row_height = self->navigator_grid->box_side * 7; } static void calculate_sizes (GcalYearView *self) { GtkStyleContext *context; PangoLayout *layout; PangoFontDescription *font; gint padding_left, padding_right, padding_top, padding_bottom; gint width, height, box_width, box_height, natural; gchar *test_str; /* get header info from CSS */ context = gtk_widget_get_style_context (GTK_WIDGET (self)); gtk_style_context_save (context); gtk_style_context_add_class (context, "first-view-header"); gtk_style_context_get (context, gtk_style_context_get_state (context), "padding-top", &padding_top, "padding-bottom", &padding_bottom, "font", &font, NULL); gtk_style_context_restore (context); /* measure header height */ test_str = g_strdup_printf ("8888"); layout = gtk_widget_create_pango_layout (self->navigator, test_str); pango_layout_set_font_description (layout, font); pango_layout_get_pixel_size (layout, &width, &height); pango_font_description_free (font); g_object_unref (layout); g_free (test_str); self->header_height = padding_top + height + padding_bottom; /* get day info from CSS */ context = gtk_widget_get_style_context (GTK_WIDGET (self->navigator)); gtk_style_context_save (context); gtk_style_context_add_class (context, "days"); gtk_style_context_get (context, gtk_style_context_get_state (context), "padding-left", &padding_left, "padding-right", &padding_right, "padding-top", &padding_top, "padding-bottom", &padding_bottom, "font", &font, NULL); gtk_style_context_restore (context); /* measure box size */ test_str = g_strdup_printf ("88"); layout = gtk_widget_create_pango_layout (self->navigator, test_str); pango_layout_set_font_description (layout, font); pango_layout_get_pixel_size (layout, &width, &height); pango_font_description_free (font); g_object_unref (layout); g_free (test_str); box_width = padding_left + width + padding_right; box_height = padding_top + height + padding_bottom; self->navigator_grid->box_side = (box_width > box_height) ? box_width : box_height; /* get sidebar width */ gtk_widget_get_preferred_width (self->sidebar, &width, &natural); self->sidebar_width = width; calculate_grid_sizes (self); } /* * Drag and Drop functions */ static gboolean navigator_drag_motion_cb (GcalYearView *self, GdkDragContext *context, gint x, gint y, guint time, GtkWidget *navigator) { gint day, month; gboolean is_title, retval; retval = FALSE; self->selected_data->dnd_day = -1; self->selected_data->dnd_month = -1; if (calculate_day_month_for_coord (self, x, y, &day, &month, &is_title)) { /* For now, don't allow dropping events on the month title */ if (!is_title) { self->selected_data->dnd_day = day; self->selected_data->dnd_month = month; gdk_drag_status (context, GDK_ACTION_COPY, time); retval = TRUE; } } else { gdk_drag_status (context, 0, time); } gtk_widget_queue_draw (self->navigator); return retval; } static gboolean navigator_drag_drop_cb (GcalYearView *self, GdkDragContext *context, gint x, gint y, guint time, GtkWidget *navigator) { gint day, month, diff; gboolean is_title; if (calculate_day_month_for_coord (self, x, y, &day, &month, &is_title)) { if (!is_title) { GcalEventWidget *event_widget; GcalEvent *event; GDateTime *start_dt, *end_dt; GDateTime *drop_date; event_widget = GCAL_EVENT_WIDGET (gtk_drag_get_source_widget (context)); event = gcal_event_widget_get_event (event_widget); start_dt = gcal_event_get_date_start (event); end_dt = gcal_event_get_date_end (event); /* * The difference is calculated in number of days. Here we have to * use the start_dt's hour/minute/second since otherwise we may not * calculate the difference correctly. */ drop_date = g_date_time_new_local (self->date->year, month + 1, day, g_date_time_get_hour (start_dt), g_date_time_get_minute (start_dt), g_date_time_get_second (start_dt)); diff = g_date_time_difference (drop_date, start_dt) / G_TIME_SPAN_DAY; if (diff != 0) { GDateTime *new_start; new_start = g_date_time_add_days (start_dt, diff); gcal_event_set_date_start (event, new_start); /* The event may have a NULL end date, so we have to check it here */ if (end_dt) { GDateTime *new_end; new_end = g_date_time_add_days (end_dt, diff); gcal_event_set_date_end (event, new_start); g_clear_pointer (&new_end, g_date_time_unref); } gcal_manager_update_event (self->manager, event); g_clear_pointer (&new_start, g_date_time_unref); } g_clear_pointer (&drop_date, g_date_time_unref); } } /* Cancel the DnD after the event is dropped */ self->selected_data->dnd_day = -1; self->selected_data->dnd_month = -1; gtk_drag_finish (context, TRUE, FALSE, time); gtk_widget_queue_draw (navigator); return TRUE; } static void navigator_drag_leave_cb (GcalYearView *self, GdkDragContext *context, guint time, GtkWidget *navigator) { /* Cancel the DnD after the event is dropped */ self->selected_data->dnd_day = -1; self->selected_data->dnd_month = -1; gtk_widget_queue_draw (navigator); } static void gcal_year_view_finalize (GObject *object) { GcalYearView *year_view = GCAL_YEAR_VIEW (object); g_free (year_view->navigator_grid); g_free (year_view->selected_data); g_free (year_view->start_selected_date); g_free (year_view->end_selected_date); g_clear_pointer (&year_view->date, g_free); g_clear_object (&year_view->shell_settings); G_OBJECT_CLASS (gcal_year_view_parent_class)->finalize (object); } static void gcal_year_view_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GcalYearView *self = GCAL_YEAR_VIEW (object); switch (prop_id) { case PROP_DATE: g_value_set_boxed (value, self->date); break; case PROP_SHOW_WEEK_NUMBERS: g_value_set_boolean (value, self->show_week_numbers); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gcal_year_view_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GcalYearView *self = GCAL_YEAR_VIEW (object); switch (prop_id) { case PROP_DATE: update_date (self, g_value_dup_boxed (value)); break; case PROP_SHOW_WEEK_NUMBERS: if (self->show_week_numbers != g_value_get_boolean (value)) { self->show_week_numbers = g_value_get_boolean (value); calculate_grid_sizes (self); g_object_notify (object, "show-week-numbers"); gtk_widget_queue_allocate (GTK_WIDGET (self)); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gcal_year_view_get_preferred_width (GtkWidget *widget, gint *minimum, gint *natural) { GcalYearView *year_view = GCAL_YEAR_VIEW (widget); GtkStyleContext *context; gint padding_left, padding_right, hpadding; context = gtk_widget_get_style_context (year_view->navigator); gtk_style_context_get (context, gtk_style_context_get_state (context), "padding-left", &padding_left, "padding-right", &padding_right, NULL); hpadding = padding_left + padding_right; if (minimum != NULL) *minimum = hpadding + year_view->column_width; if (natural != NULL) *natural = hpadding + year_view->column_width + year_view->sidebar_width; } static void gcal_year_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc) { GcalYearView *year_view = GCAL_YEAR_VIEW (widget); GtkStyleContext *context; gint padding_left, padding_right, padding_top, padding_bottom, hpadding, vpadding; gint number_of_rows; context = gtk_widget_get_style_context (widget); gtk_style_context_save (context); gtk_style_context_add_class (context, "year-navigator"); gtk_style_context_get (context, gtk_style_context_get_state (context), "padding-left", &padding_left, "padding-right", &padding_right, "padding-top", &padding_top, "padding-bottom", &padding_bottom, NULL); gtk_style_context_restore (context); hpadding = padding_left + padding_right; vpadding = padding_top + padding_bottom; year_view->popover_mode = (alloc->width < (hpadding + year_view->column_width * 2 + year_view->sidebar_width)); year_view->number_of_columns = (alloc->width - hpadding - year_view->sidebar_width * !year_view->popover_mode) / year_view->column_width; if (year_view->number_of_columns > NAVIGATOR_MAX_GRID_SIZE) year_view->number_of_columns = NAVIGATOR_MAX_GRID_SIZE; number_of_rows = ceil (12.0 / year_view->number_of_columns); gtk_widget_set_size_request (year_view->navigator, hpadding + year_view->column_width * year_view->number_of_columns, vpadding + year_view->row_height * number_of_rows + year_view->header_height); if (year_view->popover_mode && !gtk_widget_is_ancestor (year_view->events_sidebar, year_view->popover)) { g_object_ref (year_view->sidebar); gtk_container_remove (GTK_CONTAINER (widget), year_view->sidebar); gtk_container_add (GTK_CONTAINER (year_view->popover), year_view->sidebar); g_object_unref (year_view->sidebar); gtk_widget_show_all (year_view->sidebar); popover_closed_cb (GCAL_YEAR_VIEW (widget), GTK_POPOVER (year_view->popover)); } else if (!year_view->popover_mode && gtk_widget_is_ancestor (year_view->events_sidebar, year_view->popover)) { g_object_ref (year_view->sidebar); gtk_container_remove (GTK_CONTAINER (year_view->popover), year_view->sidebar); gtk_box_pack_end (GTK_BOX (widget), year_view->sidebar, FALSE, TRUE, 0); g_object_unref (year_view->sidebar); gtk_widget_show (year_view->sidebar); g_signal_handlers_block_by_func (year_view->popover, popover_closed_cb, widget); gtk_widget_hide (year_view->popover); g_signal_handlers_unblock_by_func (year_view->popover, popover_closed_cb, widget); } GTK_WIDGET_CLASS (gcal_year_view_parent_class)->size_allocate (widget, alloc); } static void gcal_year_view_direction_changed (GtkWidget *widget, GtkTextDirection previous_direction) { GcalYearView *year_view = GCAL_YEAR_VIEW (widget); if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) year_view->k = 0; else if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) year_view->k = 1; } static GList* gcal_year_view_get_children_by_uuid (GcalView *view, const gchar *uuid) { GcalYearView *year_view = GCAL_YEAR_VIEW (view); GList *children, *l, *result = NULL; children = gtk_container_get_children (GTK_CONTAINER (year_view->events_sidebar)); for (l = children; l != NULL; l = g_list_next (l)) { GcalEventWidget *child_widget; GcalEvent *event; child_widget = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (l->data))); event = gcal_event_widget_get_event (child_widget); if (child_widget != NULL && g_strcmp0 (uuid, gcal_event_get_uid (event)) == 0) result = g_list_append (result, child_widget); } g_list_free (children); return result; } static void gcal_year_view_component_added (ECalDataModelSubscriber *subscriber, ECalClient *client, ECalComponent *comp) { GcalYearView *year_view = GCAL_YEAR_VIEW (subscriber); GcalEvent *event; GList **days_widgets_array; GList *l; gint i, days_span; ECalComponentDateTime date; time_t event_start, event_end, range_start, range_end; icaltimezone *zone; update_selected_dates_from_button_data (year_view); days_span = icaltime_day_of_year(*(year_view->end_selected_date)) - icaltime_day_of_year(*(year_view->start_selected_date)) + 1; days_widgets_array = g_new0 (GList*, days_span); event = gcal_event_new (e_client_get_source (E_CLIENT (client)), comp); /* check if event belongs to range */ zone = gcal_manager_get_system_timezone (year_view->manager); range_start = icaltime_as_timet_with_zone (*(year_view->start_selected_date), zone); range_end = icaltime_as_timet_with_zone (*(year_view->end_selected_date), zone); e_cal_component_get_dtstart (comp, &date); event_start = icaltime_as_timet_with_zone (*(date.value), date.value->zone != NULL ? date.value->zone : zone); e_cal_component_free_datetime (&date); e_cal_component_get_dtend (comp, &date); if (date.value != NULL) event_end = icaltime_as_timet_with_zone (*(date.value), date.value->zone != NULL ? date.value->zone : zone); else event_end = event_start; e_cal_component_free_datetime (&date); if (!((event_start <= range_start && event_end >= range_end) || (event_start >= range_start && event_end <= range_end) || (event_start >= range_start && event_start <= range_end) || (event_end >= range_start && event_end <= range_end))) { goto out; } add_event_to_day_array (year_view, event, days_widgets_array, days_span); gtk_stack_set_visible_child_name (GTK_STACK (year_view->navigator_stack), "events-list"); for (i = 0; i < days_span; i++) { GList *current_day = days_widgets_array[i]; for (l = current_day; l != NULL; l = g_list_next (l)) { GtkWidget *child_widget = l->data; gtk_widget_show (child_widget); g_signal_connect (child_widget, "activate", G_CALLBACK (event_activated), year_view); g_object_set_data (G_OBJECT (child_widget), "shift", GINT_TO_POINTER (i)); gtk_container_add (GTK_CONTAINER (year_view->events_sidebar), child_widget); } g_list_free (current_day); } gtk_widget_queue_draw (year_view->navigator); out: g_clear_object (&event); g_free (days_widgets_array); } static void gcal_year_view_component_changed (ECalDataModelSubscriber *subscriber, ECalClient *client, ECalComponent *comp) { GcalYearView *year_view = GCAL_YEAR_VIEW (subscriber); GList *children, *l; gchar *uuid; uuid = get_uuid_from_component (e_client_get_source (E_CLIENT (client)), comp); children = gtk_container_get_children (GTK_CONTAINER (year_view->events_sidebar)); for (l = children; l != NULL; l = g_list_next (l)) { GcalEventWidget *child_widget; GcalEvent *event; child_widget = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (l->data))); event = gcal_event_widget_get_event (child_widget); if (child_widget != NULL && g_strcmp0 (uuid, gcal_event_get_uid (event)) == 0) gtk_widget_destroy (GTK_WIDGET (l->data)); } g_list_free (children); g_free (uuid); gcal_year_view_component_added (subscriber, client, comp); } static void gcal_year_view_component_removed (ECalDataModelSubscriber *subscriber, ECalClient *client, const gchar *uid, const gchar *rid) { GcalYearView *year_view = GCAL_YEAR_VIEW (subscriber); GList *children, *l; ESource *source; gchar *uuid; gboolean update_sidebar_needed = FALSE; source = e_client_get_source (E_CLIENT (client)); if (rid != NULL) uuid = g_strdup_printf ("%s:%s:%s", e_source_get_uid (source), uid, rid); else uuid = g_strdup_printf ("%s:%s", e_source_get_uid (source), uid); children = gtk_container_get_children (GTK_CONTAINER (year_view->events_sidebar)); for (l = children; l != NULL; l = g_list_next (l)) { GcalEventWidget *child_widget; GcalEvent *event; child_widget = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (l->data))); event = gcal_event_widget_get_event (child_widget); if (child_widget != NULL && g_strcmp0 (uuid, gcal_event_get_uid (event)) == 0) { if (g_list_length (children) == 1) update_sidebar_needed = TRUE; gtk_widget_destroy (GTK_WIDGET (l->data)); } } g_list_free (children); g_free (uuid); if (update_sidebar_needed) { update_no_events_page (GCAL_YEAR_VIEW (subscriber)); gtk_stack_set_visible_child_name (GTK_STACK (year_view->navigator_stack), "no-events"); } } static void gcal_year_view_freeze (ECalDataModelSubscriber *subscriber) { ; } static void gcal_year_view_thaw (ECalDataModelSubscriber *subscriber) { } static void gcal_year_view_class_init (GcalYearViewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = gcal_year_view_finalize; object_class->get_property = gcal_year_view_get_property; object_class->set_property = gcal_year_view_set_property; widget_class->get_preferred_width = gcal_year_view_get_preferred_width; widget_class->size_allocate = gcal_year_view_size_allocate; widget_class->direction_changed = gcal_year_view_direction_changed; g_object_class_override_property (object_class, PROP_DATE, "active-date"); g_object_class_install_property (object_class, PROP_SHOW_WEEK_NUMBERS, g_param_spec_boolean ("show-week-numbers", "Show Week Numbers", "Show Week Numbers Column", FALSE, G_PARAM_READWRITE)); /* FIXME: it will problably go back to GcalView */ signals[EVENT_ACTIVATED] = g_signal_new ("event-activated", GCAL_TYPE_YEAR_VIEW, G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, GCAL_TYPE_EVENT_WIDGET); gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/year-view.ui"); gtk_widget_class_bind_template_child (widget_class, GcalYearView, navigator); gtk_widget_class_bind_template_child (widget_class, GcalYearView, sidebar); gtk_widget_class_bind_template_child (widget_class, GcalYearView, events_sidebar); gtk_widget_class_bind_template_child (widget_class, GcalYearView, navigator_stack); gtk_widget_class_bind_template_child (widget_class, GcalYearView, navigator_sidebar); gtk_widget_class_bind_template_child (widget_class, GcalYearView, no_events_title); gtk_widget_class_bind_template_child (widget_class, GcalYearView, popover); gtk_widget_class_bind_template_callback (widget_class, draw_navigator); gtk_widget_class_bind_template_callback (widget_class, navigator_button_press_cb); gtk_widget_class_bind_template_callback (widget_class, navigator_button_release_cb); gtk_widget_class_bind_template_callback (widget_class, navigator_drag_drop_cb); gtk_widget_class_bind_template_callback (widget_class, navigator_drag_leave_cb); gtk_widget_class_bind_template_callback (widget_class, navigator_drag_motion_cb); gtk_widget_class_bind_template_callback (widget_class, navigator_motion_notify_cb); gtk_widget_class_bind_template_callback (widget_class, add_event_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, popover_closed_cb); gtk_widget_class_set_css_name (widget_class, "calendar-view"); } static void gcal_year_view_init (GcalYearView *self) { gtk_widget_init_template (GTK_WIDGET (self)); if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_LTR) self->k = 0; else if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) self->k = 1; self->navigator_grid = g_new0 (GridData, 1); self->selected_data = g_new0 (ButtonData, 1); self->start_selected_date = g_new0 (icaltimetype, 1); self->start_selected_date->zone = e_cal_util_get_system_timezone (); self->end_selected_date = g_new0 (icaltimetype, 1); self->end_selected_date->zone = e_cal_util_get_system_timezone (); /* bind GNOME Shell' show week numbers property to GNOME Calendar's one */ self->shell_settings = g_settings_new ("org.gnome.shell.calendar"); g_settings_bind (self->shell_settings, "show-weekdate", self, "show-week-numbers", G_SETTINGS_BIND_DEFAULT); g_signal_connect_swapped (self->shell_settings, "changed::show-weekdate", G_CALLBACK (gtk_widget_queue_draw), self); /* layout */ self->number_of_columns = 4; calculate_sizes (self); gtk_list_box_set_header_func (GTK_LIST_BOX (self->events_sidebar), update_sidebar_headers, self, NULL); gtk_list_box_set_sort_func (GTK_LIST_BOX (self->events_sidebar), sidebar_sort_func, NULL, NULL); /* The year navigator is a DnD destination */ gtk_drag_dest_set (self->navigator, 0, NULL, 0, GDK_ACTION_COPY); } static void gcal_view_interface_init (GcalViewInterface *iface) { /* FIXME: implement what's needed */ iface->get_children_by_uuid = gcal_year_view_get_children_by_uuid; } static void gcal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface) { iface->component_added = gcal_year_view_component_added; iface->component_modified = gcal_year_view_component_changed; iface->component_removed = gcal_year_view_component_removed; iface->freeze = gcal_year_view_freeze; iface->thaw = gcal_year_view_thaw; } /* Public API */ void gcal_year_view_set_manager (GcalYearView *year_view, GcalManager *manager) { year_view->manager = manager; } void gcal_year_view_set_first_weekday (GcalYearView *year_view, gint nr_day) { year_view->first_weekday = nr_day; } void gcal_year_view_set_use_24h_format (GcalYearView *year_view, gboolean use_24h_format) { year_view->use_24h_format = use_24h_format; } void gcal_year_view_set_current_date (GcalYearView *year_view, icaltimetype *current_date) { year_view->current_date = current_date; /* Launches update */ gtk_widget_queue_draw (GTK_WIDGET (year_view)); update_sidebar (year_view); }