Files
federated-calendar/src/gcal-year-view.c
T
Isaque Galdino 3dbe4addeb year-view: Popover position
Year view was placing popover to mouse click (x,y) position. That was a
little odd because sometimes popover was placed above the selected day.

This patch address that by placing popover always out of the day box.

When selecting the whole month or more than one day, popover will also
be placed out of the month box. reducing the chance to cover selected
days.

https://bugzilla.gnome.org/show_bug.cgi?id=743306
2016-06-17 18:00:32 -03:00

1913 lines
65 KiB
C

/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* gcal-year-view.c
*
* Copyright (C) 2015 Erick Pérez Castellanos <erick.red@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "gcal-year-view.h"
#include "gcal-view.h"
#include "gcal-utils.h"
#include <glib/gi18n.h>
#include <math.h>
#include <string.h>
#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 *calendar_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 void
calculate_coord_for_date (GcalYearView *year_view,
gint day,
gint month,
gboolean is_title,
GdkRectangle *rect)
{
gint cell_x, cell_y, clicked_cell, sw;
if (is_title ||
year_view->selected_data->start_day != year_view->selected_data->end_day ||
year_view->selected_data->start_month != year_view->selected_data->end_month)
{
rect->x = year_view->navigator_grid->coordinates[month].x;
rect->y = year_view->navigator_grid->coordinates[month].y;
rect->width = year_view->column_width;
rect->height = year_view->row_height;
return;
}
/* else */
sw = 1 - 2 * year_view->k;
clicked_cell = day + ((time_day_of_week (1, month, year_view->date->year) - year_view->first_weekday + 7) % 7) - 1;
cell_x = (clicked_cell % 7 + year_view->k + year_view->show_week_numbers) * year_view->navigator_grid->box_side * sw;
cell_x += (year_view->k * year_view->navigator_grid->box_side * (7 + year_view->show_week_numbers));
cell_y = ((clicked_cell / 7 + 1) * year_view->navigator_grid->box_side);
rect->x = cell_x + year_view->navigator_grid->coordinates[month].x;
rect->y = cell_y + year_view->navigator_grid->coordinates[month].y;
rect->width = rect->height = year_view->navigator_grid->box_side;
}
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 + 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 + 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);
calculate_coord_for_date (year_view, day, month, is_title, &rect);
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->calendar_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->calendar_settings = g_settings_new ("org.gnome.desktop.calendar");
g_settings_bind (self->calendar_settings, "show-weekdate", self, "show-week-numbers", G_SETTINGS_BIND_DEFAULT);
g_signal_connect_swapped (self->calendar_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);
}