The view knows its time range, and knows what the next or previous dates should be. So instead of calculating the next date manually, ask the views for that.
2161 lines
72 KiB
C
2161 lines
72 KiB
C
/* 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/>.
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "GcalYearView"
|
|
|
|
#include "gcal-application.h"
|
|
#include "gcal-clock.h"
|
|
#include "gcal-context.h"
|
|
#include "gcal-debug.h"
|
|
#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 *scrolled_window;
|
|
GtkLabel *temp_label; /* unowned */
|
|
GtkImage *weather_icon; /* unowned */
|
|
|
|
GtkWidget *popover; /* Popover for popover_mode */
|
|
|
|
/* manager singleton */
|
|
GcalContext *context;
|
|
|
|
/* range shown on the sidebar */
|
|
GDateTime *start_selected_date;
|
|
GDateTime *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;
|
|
|
|
/* Storage for the accumulated scrolling */
|
|
gdouble scroll_value;
|
|
|
|
/* show week numbers from GNOME Shell settings */
|
|
GSettings *calendar_settings;
|
|
gboolean show_week_numbers;
|
|
|
|
/* text direction factors */
|
|
gint k;
|
|
|
|
/* date property */
|
|
GDateTime *date;
|
|
|
|
/*
|
|
* Array with the events at every month. Events
|
|
* that span multiple months are added multiple
|
|
* times to the array.
|
|
*/
|
|
GPtrArray *events[12];
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_DATE,
|
|
PROP_CONTEXT,
|
|
PROP_SHOW_WEEK_NUMBERS,
|
|
LAST_PROP
|
|
};
|
|
|
|
static void gcal_view_interface_init (GcalViewInterface *iface);
|
|
static void gcal_data_model_subscriber_interface_init (ECalDataModelSubscriberInterface *iface);
|
|
static void update_weather (GcalYearView *self);
|
|
|
|
|
|
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
|
|
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_by_name (view, "event-activated", widget);
|
|
}
|
|
|
|
static void
|
|
weather_changed (GcalWeatherService *weather_service,
|
|
GcalYearView *self)
|
|
{
|
|
g_return_if_fail (GCAL_IS_WEATHER_SERVICE (weather_service));
|
|
g_return_if_fail (GCAL_IS_YEAR_VIEW (self));
|
|
|
|
update_weather (self);
|
|
}
|
|
|
|
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);
|
|
|
|
gcal_clear_date_time (&year_view->start_selected_date);
|
|
year_view->start_selected_date = g_date_time_new_local (g_date_time_get_year (year_view->date),
|
|
selected_data.start_month + 1,
|
|
selected_data.start_day,
|
|
0, 0, 0);
|
|
|
|
gcal_clear_date_time (&year_view->end_selected_date);
|
|
year_view->end_selected_date = g_date_time_new_local (g_date_time_get_year (year_view->date),
|
|
selected_data.end_month + 1,
|
|
selected_data.end_day,
|
|
23, 59, 59);
|
|
}
|
|
else
|
|
{
|
|
g_autoptr (GDateTime) start_date = NULL;
|
|
|
|
if (year_view->date)
|
|
start_date = g_date_time_ref (year_view->date);
|
|
else
|
|
start_date = g_date_time_new_now_local ();
|
|
|
|
gcal_clear_date_time (&year_view->start_selected_date);
|
|
year_view->start_selected_date = g_date_time_new (gcal_context_get_timezone (year_view->context),
|
|
g_date_time_get_year (start_date),
|
|
g_date_time_get_month (start_date),
|
|
g_date_time_get_day_of_month (start_date),
|
|
0, 0, 0);
|
|
|
|
gcal_clear_date_time (&year_view->end_selected_date);
|
|
year_view->end_selected_date = g_date_time_add_days (year_view->start_selected_date, 1);
|
|
|
|
year_view->selected_data->start_day = g_date_time_get_day_of_month (year_view->start_selected_date);
|
|
year_view->selected_data->start_month = g_date_time_get_month (year_view->start_selected_date) - 1;
|
|
year_view->selected_data->end_day = g_date_time_get_day_of_month (year_view->end_selected_date);
|
|
year_view->selected_data->end_month = g_date_time_get_month (year_view->end_selected_date)-1;
|
|
}
|
|
|
|
if (g_date_time_get_year (year_view->end_selected_date) != g_date_time_get_year (year_view->start_selected_date))
|
|
{
|
|
g_autoptr (GDateTime) end_of_year = NULL;
|
|
|
|
end_of_year = g_date_time_new (gcal_context_get_timezone (year_view->context),
|
|
g_date_time_get_year (year_view->start_selected_date),
|
|
G_DATE_DECEMBER,
|
|
31,
|
|
0, 0, 0);
|
|
gcal_set_date_time (&year_view->end_selected_date, end_of_year);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_no_events_page (GcalYearView *year_view)
|
|
{
|
|
g_autoptr (GDateTime) now = NULL;
|
|
gboolean has_range;
|
|
gchar *title;
|
|
|
|
now = g_date_time_new_now_local ();
|
|
has_range = gcal_date_time_compare_date (year_view->start_selected_date, year_view->end_selected_date);
|
|
|
|
if (gcal_date_time_compare_date (now, year_view->start_selected_date) == 0)
|
|
{
|
|
title = g_strdup_printf ("%s%s", _("Today"), has_range ? "…" : "");
|
|
}
|
|
else
|
|
{
|
|
if (has_range)
|
|
{
|
|
/* Translators: This is a date format in the sidebar of the year
|
|
* view when the selection starts at the specified day and the
|
|
* end is unspecified. */
|
|
title = g_date_time_format (year_view->start_selected_date, _("%B %d…"));
|
|
}
|
|
else
|
|
{
|
|
/* Translators: This is a date format in the sidebar of the year
|
|
* view when there is only one specified day selected. */
|
|
title = g_date_time_format (year_view->start_selected_date, _("%B %d"));
|
|
}
|
|
}
|
|
|
|
gtk_label_set_text (GTK_LABEL (year_view->no_events_title), title);
|
|
update_weather (year_view);
|
|
g_free (title);
|
|
}
|
|
|
|
static void
|
|
add_event_to_day_array (GcalYearView *year_view,
|
|
GcalEvent *event,
|
|
GList **days_widgets_array,
|
|
gint days_span)
|
|
{
|
|
g_autoptr (GDateTime) second_date = NULL;
|
|
g_autoptr (GDateTime) date = NULL;
|
|
GcalCalendar *calendar;
|
|
GtkWidget *child_widget;
|
|
GDateTime *dt_start, *dt_end;
|
|
gboolean is_readonly;
|
|
gint i;
|
|
|
|
calendar = gcal_event_get_calendar (event);
|
|
is_readonly = gcal_calendar_is_read_only (calendar);
|
|
|
|
child_widget = gcal_event_widget_new (year_view->context, event);
|
|
gcal_event_widget_set_read_only (GCAL_EVENT_WIDGET (child_widget), is_readonly);
|
|
|
|
dt_start = gcal_event_get_date_start (event);
|
|
dt_end = gcal_event_get_date_end (event);
|
|
|
|
/* normalize date on each new event */
|
|
date = g_date_time_ref (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 = gcal_date_time_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 = gcal_date_time_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);
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_sidebar (GcalYearView *year_view)
|
|
{
|
|
g_autofree icaltimetype *start_icaldatetime = NULL;
|
|
g_autofree icaltimetype *end_icaldatetime = NULL;
|
|
GcalManager *manager;
|
|
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 = g_date_time_get_day_of_year (year_view->end_selected_date) - g_date_time_get_day_of_year (year_view->start_selected_date) + 1;
|
|
days_widgets_array = g_new0 (GList*, days_span);
|
|
|
|
manager = gcal_context_get_manager (year_view->context);
|
|
start_icaldatetime = gcal_date_time_to_icaltime (year_view->start_selected_date);
|
|
end_icaldatetime = gcal_date_time_to_icaltime (year_view->end_selected_date);
|
|
events = gcal_manager_get_events (manager, start_icaldatetime, end_icaldatetime);
|
|
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;
|
|
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)
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
g_autoptr (GDateTime) row_day = NULL;
|
|
g_autoptr (GDateTime) now = NULL;
|
|
GtkWidget *label;
|
|
gchar *label_str;
|
|
|
|
row_header = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
|
now = g_date_time_new_now_local ();
|
|
row_day = g_date_time_add_days (year_view->start_selected_date, row_shift);
|
|
|
|
if (gcal_date_time_compare_date (row_day, now) == 0)
|
|
label_str = g_strdup (_("Today"));
|
|
else
|
|
/* Translators: This is a date format in the sidebar of the year view. */
|
|
label_str = g_date_time_format (row_day, _("%B %d"));
|
|
|
|
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 (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
|
|
update_date (GcalYearView *year_view,
|
|
GDateTime *new_date)
|
|
{
|
|
gboolean needs_reset = FALSE;
|
|
|
|
if (year_view->date &&
|
|
year_view->start_selected_date &&
|
|
!g_date_time_equal (year_view->date, new_date))
|
|
{
|
|
needs_reset = TRUE;
|
|
}
|
|
|
|
gcal_set_date_time (&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,
|
|
g_date_time_get_year (year_view->date));;
|
|
year_view->last_week_of_year = get_last_week_of_year_dmy (year_view->first_weekday,
|
|
31, G_DATE_DECEMBER,
|
|
g_date_time_get_year (year_view->date));
|
|
|
|
if (needs_reset)
|
|
reset_sidebar (year_view);
|
|
}
|
|
|
|
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, g_date_time_get_year (year_view->date)) - 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)
|
|
{
|
|
GdkPoint *coordinates;
|
|
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;
|
|
coordinates = year_view->navigator_grid->coordinates;
|
|
|
|
*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 > coordinates[i * year_view->number_of_columns].y &&
|
|
y < coordinates[i * year_view->number_of_columns].y + year_view->row_height)
|
|
{
|
|
if (y < coordinates[i * year_view->number_of_columns].y + box_side)
|
|
*is_title = TRUE;
|
|
row = i;
|
|
}
|
|
if (column == -1 &&
|
|
x > coordinates[i].x + box_side * year_view->show_week_numbers * (1 - year_view->k) &&
|
|
x < 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, g_date_time_get_year (year_view->date)) - year_view->first_weekday + 7) % 7);
|
|
|
|
if (day < 1 || day > time_days_in_month (g_date_time_get_year (year_view->date), month))
|
|
return FALSE;
|
|
|
|
*out_day = day;
|
|
return TRUE;
|
|
}
|
|
static guint
|
|
count_events_at_day (GcalYearView *self,
|
|
GDateTime *today)
|
|
{
|
|
g_autoptr (GDateTime) today_end = NULL;
|
|
GPtrArray *events;
|
|
guint i, n_events;
|
|
|
|
events = self->events[g_date_time_get_month (today) - 1];
|
|
n_events = 0;
|
|
today_end = g_date_time_add_days (today, 1);
|
|
|
|
for (i = 0; i < events->len; i++)
|
|
{
|
|
g_autoptr (GDateTime) event_start, event_end;
|
|
GcalEvent *event;
|
|
|
|
event = g_ptr_array_index (events, i);
|
|
|
|
event_start = g_date_time_ref (gcal_event_get_date_start (event));
|
|
|
|
if (gcal_event_get_all_day (event))
|
|
event_end = g_date_time_add_days (gcal_event_get_date_end (event), -1);
|
|
else
|
|
event_end = g_date_time_ref (gcal_event_get_date_end (event));
|
|
|
|
|
|
if (gcal_date_time_compare_date (event_start, today_end) >= 0 ||
|
|
gcal_date_time_compare_date (event_end, today) < 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
n_events++;
|
|
}
|
|
|
|
return n_events;
|
|
}
|
|
|
|
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;
|
|
|
|
g_autoptr (GDateTime) day = NULL;
|
|
g_autoptr (GDateTime) now = NULL;
|
|
|
|
GdkRGBA color;
|
|
gint layout_width, layout_height, i, j, sw;
|
|
gint column, row;
|
|
gboolean column_is_workday;
|
|
gdouble x, y, box_side, box_padding_top, box_padding_start;
|
|
gint days_delay, days, shown_rows;
|
|
gchar *str, *nr_day, *nr_week;
|
|
gboolean selected_day;
|
|
|
|
now = g_date_time_new_now_local ();
|
|
|
|
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, g_date_time_get_year (year_view->date)) - year_view->first_weekday + 7) % 7;
|
|
days = days_delay + g_date_get_days_in_month (month_nr + 1, g_date_time_get_year (year_view->date));
|
|
shown_rows = ceil (days / 7.0);
|
|
|
|
day = g_date_time_new (gcal_context_get_timezone (year_view->context),
|
|
g_date_time_get_year (year_view->date),
|
|
month_nr + 1,
|
|
1, 0, 0, 0);
|
|
|
|
for (i = 0; i < 7 * shown_rows; i++)
|
|
{
|
|
g_autoptr (GDateTime) next_day = NULL;
|
|
|
|
column = (i % 7) + (year_view->k || year_view->show_week_numbers);
|
|
column_is_workday = is_workday ((7 * year_view->k + sw * (column - (year_view->show_week_numbers * !year_view->k) + (sw * year_view->first_weekday))) % 7);
|
|
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 (g_date_time_get_year (year_view->date) == g_date_time_get_year (now) &&
|
|
month_nr + 1 == g_date_time_get_month (now) &&
|
|
j == g_date_time_get_day_of_month (now))
|
|
{
|
|
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_is_workday)
|
|
{
|
|
gtk_style_context_save (context);
|
|
gtk_style_context_add_class (context, "non-workday");
|
|
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);
|
|
}
|
|
|
|
if (count_events_at_day (year_view, day) > 0)
|
|
{
|
|
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_is_workday)
|
|
gtk_style_context_add_class (context, "with-events-non-workday");
|
|
|
|
box_padding_start = MAX (0, (box_side - VISUAL_CLUES_SIDE) / 2);
|
|
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);
|
|
}
|
|
|
|
/* Update the current day, so we can count the events at this day */
|
|
next_day = g_date_time_add_days (day, 1);
|
|
gcal_set_date_time (&day, next_day);
|
|
|
|
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;
|
|
GtkBorder padding;
|
|
|
|
gint 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 (widget);
|
|
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, "primary-label");
|
|
|
|
header_str = g_strdup_printf ("%d", g_date_time_get_year (year_view->date));
|
|
gtk_style_context_get (context, state_flags, "font", &font_desc, NULL);
|
|
gtk_style_context_get_padding (context, state_flags, &padding);
|
|
|
|
gtk_style_context_restore (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 * padding.left,
|
|
padding.top,
|
|
header_layout);
|
|
|
|
pango_font_description_free (font_desc);
|
|
g_object_unref (header_layout);
|
|
g_free (header_str);
|
|
|
|
header_height = 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 GDK_EVENT_PROPAGATE;
|
|
|
|
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 GDK_EVENT_PROPAGATE;
|
|
}
|
|
|
|
static gboolean
|
|
navigator_button_release_cb (GcalYearView *year_view,
|
|
GdkEventButton *event,
|
|
GtkWidget *widget)
|
|
{
|
|
g_autoptr (GDateTime) new_date = NULL;
|
|
gboolean is_title = FALSE;
|
|
gint day, month;
|
|
|
|
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, g_date_time_get_year (year_view->date));
|
|
|
|
year_view->button_pressed = FALSE;
|
|
year_view->selected_data->end_day = day;
|
|
year_view->selected_data->end_month = month;
|
|
|
|
/* update date and notify */
|
|
new_date = g_date_time_new_local (g_date_time_get_year (year_view->date),
|
|
month + 1,
|
|
day,
|
|
0, 0, 0);
|
|
gcal_set_date_time (&year_view->date, new_date);
|
|
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 = 0, month = 0;
|
|
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, g_date_time_get_year (year_view->date));
|
|
|
|
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
|
|
navigator_edge_overshot_cb (GcalYearView *self,
|
|
GtkPositionType position_type)
|
|
{
|
|
g_autoptr (GDateTime) new_date = NULL;
|
|
GtkAdjustment *adjustment;
|
|
|
|
/* Ignore horizontal scrolling (and the year view never scrolls horizontally anyway) */
|
|
if (position_type == GTK_POS_LEFT || position_type == GTK_POS_RIGHT)
|
|
return;
|
|
|
|
adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolled_window));
|
|
|
|
new_date = g_date_time_add_years (self->date, position_type == GTK_POS_BOTTOM ? 1 : -1);
|
|
gcal_set_date_time (&self->date, new_date);
|
|
|
|
gtk_adjustment_set_value (adjustment, 0.0);
|
|
gtk_widget_queue_draw (self->navigator);
|
|
|
|
g_object_notify (G_OBJECT (self), "active-date");
|
|
}
|
|
|
|
static gboolean
|
|
navigator_scroll_event_cb (GcalYearView *self,
|
|
GdkEventScroll *scroll_event)
|
|
{
|
|
GtkWidget *vscrollbar;
|
|
|
|
vscrollbar = gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (self->scrolled_window));
|
|
|
|
/*
|
|
* If the vertical scrollbar is visible, delegate the year changing check
|
|
* to GtkScrolledWindow:edge-overshot callback.
|
|
*/
|
|
if (gtk_widget_get_child_visible (vscrollbar))
|
|
return GDK_EVENT_PROPAGATE;
|
|
|
|
/*
|
|
* If we accumulated enough scrolling, change the month. Otherwise, we'd scroll
|
|
* waaay too fast.
|
|
*/
|
|
if (should_change_date_for_scroll (&self->scroll_value, scroll_event))
|
|
{
|
|
g_autoptr (GDateTime) new_date = NULL;
|
|
|
|
new_date = g_date_time_add_years (self->date, self->scroll_value > 0 ? 1 : -1);
|
|
gcal_set_date_time (&self->date, new_date);
|
|
self->scroll_value = 0;
|
|
|
|
gtk_widget_queue_draw (self->navigator);
|
|
|
|
g_object_notify (G_OBJECT (self), "active-date");
|
|
}
|
|
|
|
return GDK_EVENT_STOP;
|
|
}
|
|
|
|
static void
|
|
add_event_clicked_cb (GcalYearView *year_view,
|
|
GtkButton *button)
|
|
{
|
|
g_autoptr (GDateTime) start_date = NULL;
|
|
g_autoptr (GDateTime) end_date = NULL;
|
|
|
|
if (year_view->start_selected_date)
|
|
{
|
|
start_date = g_date_time_new_now_local ();
|
|
}
|
|
else
|
|
{
|
|
start_date = g_date_time_ref (year_view->start_selected_date);
|
|
end_date = g_date_time_add_days (year_view->end_selected_date, 1);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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, "primary-label");
|
|
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_MOVE, 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;
|
|
gboolean is_title;
|
|
|
|
if (calculate_day_month_for_coord (self, x, y, &day, &month, &is_title))
|
|
{
|
|
if (!GCAL_IS_EVENT_WIDGET (gtk_drag_get_source_widget (context)))
|
|
return FALSE;
|
|
|
|
if (!is_title)
|
|
{
|
|
GcalRecurrenceModType mod;
|
|
GcalEventWidget *event_widget;
|
|
GcalCalendar *calendar;
|
|
GcalEvent *event;
|
|
GDateTime *start_dt, *end_dt;
|
|
GDateTime *drop_date;
|
|
ESource *source;
|
|
|
|
event_widget = GCAL_EVENT_WIDGET (gtk_drag_get_source_widget (context));
|
|
event = gcal_event_widget_get_event (event_widget);
|
|
calendar = gcal_event_get_calendar (event);
|
|
source = gcal_calendar_get_source (calendar);
|
|
mod = GCAL_RECURRENCE_MOD_THIS_ONLY;
|
|
|
|
if (gcal_event_has_recurrence (event) &&
|
|
!ask_recurrence_modification_type (GTK_WIDGET (self), &mod, source))
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
start_dt = gcal_event_get_date_start (event);
|
|
end_dt = gcal_event_get_date_end (event);
|
|
|
|
drop_date = g_date_time_add_full (start_dt,
|
|
g_date_time_get_year (self->date) - g_date_time_get_year (start_dt),
|
|
(month + 1) - g_date_time_get_month (start_dt),
|
|
day - g_date_time_get_day_of_month (start_dt),
|
|
0, 0, 0);
|
|
|
|
if (!g_date_time_equal (start_dt, drop_date))
|
|
{
|
|
GTimeSpan diff = g_date_time_difference (drop_date, start_dt);
|
|
GDateTime *new_start;
|
|
|
|
new_start = g_date_time_add (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 (end_dt, diff);
|
|
gcal_event_set_date_end (event, new_end);
|
|
|
|
g_clear_pointer (&new_end, g_date_time_unref);
|
|
}
|
|
|
|
gcal_manager_update_event (gcal_context_get_manager (self->context), event, mod);
|
|
|
|
g_clear_pointer (&new_start, g_date_time_unref);
|
|
}
|
|
|
|
g_clear_pointer (&drop_date, g_date_time_unref);
|
|
}
|
|
}
|
|
|
|
out:
|
|
/* 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);
|
|
}
|
|
|
|
/* GcalView implementation */
|
|
static GDateTime*
|
|
gcal_year_view_get_date (GcalView *view)
|
|
{
|
|
GcalYearView *self = GCAL_YEAR_VIEW (view);
|
|
|
|
return self->date;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_set_date (GcalView *view,
|
|
GDateTime *date)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
update_date (GCAL_YEAR_VIEW (view), date);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static GList*
|
|
gcal_year_view_get_children_by_uuid (GcalView *view,
|
|
GcalRecurrenceModType mod,
|
|
const gchar *uuid)
|
|
{
|
|
GcalYearView *self = GCAL_YEAR_VIEW (view);
|
|
GList *result, *children;
|
|
|
|
result = NULL;
|
|
|
|
children = gtk_container_get_children (GTK_CONTAINER (self->sidebar));
|
|
result = filter_event_list_by_uid_and_modtype (children, mod, uuid);
|
|
|
|
g_list_free (children);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_update_subscription (GcalView *view)
|
|
{
|
|
g_autoptr (GDateTime) date_start = NULL;
|
|
g_autoptr (GDateTime) date_end = NULL;
|
|
GcalYearView *self;
|
|
time_t range_start;
|
|
time_t range_end;
|
|
|
|
self = GCAL_YEAR_VIEW (view);
|
|
date_start = g_date_time_new_local (g_date_time_get_year (self->date), 1, 1, 0, 0, 0);
|
|
range_start = g_date_time_to_unix (date_start);
|
|
|
|
date_end = g_date_time_add_years (date_start, 1);
|
|
range_end = g_date_time_to_unix (date_end);
|
|
|
|
gcal_manager_set_subscriber (gcal_context_get_manager (self->context),
|
|
E_CAL_DATA_MODEL_SUBSCRIBER (self),
|
|
range_start,
|
|
range_end);
|
|
}
|
|
|
|
static GDateTime*
|
|
gcal_year_view_get_next_date (GcalView *view)
|
|
{
|
|
GcalYearView *self = GCAL_YEAR_VIEW (view);
|
|
|
|
g_assert (self->date != NULL);
|
|
return g_date_time_add_years (self->date, 1);
|
|
}
|
|
|
|
|
|
static GDateTime*
|
|
gcal_year_view_get_previous_date (GcalView *view)
|
|
{
|
|
GcalYearView *self = GCAL_YEAR_VIEW (view);
|
|
|
|
g_assert (self->date != NULL);
|
|
return g_date_time_add_years (self->date, -1);
|
|
}
|
|
|
|
static void
|
|
gcal_view_interface_init (GcalViewInterface *iface)
|
|
{
|
|
/* FIXME: implement what's needed */
|
|
iface->get_date = gcal_year_view_get_date;
|
|
iface->set_date = gcal_year_view_set_date;
|
|
iface->get_children_by_uuid = gcal_year_view_get_children_by_uuid;
|
|
iface->update_subscription = gcal_year_view_update_subscription;
|
|
iface->get_next_date = gcal_year_view_get_next_date;
|
|
iface->get_previous_date = gcal_year_view_get_previous_date;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_finalize (GObject *object)
|
|
{
|
|
GcalYearView *year_view = GCAL_YEAR_VIEW (object);
|
|
guint i;
|
|
|
|
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);
|
|
|
|
for (i = 0; i < 12; i++)
|
|
g_clear_pointer (&year_view->events[i], g_ptr_array_unref);
|
|
|
|
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_CONTEXT:
|
|
g_value_set_object (value, self->context);
|
|
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:
|
|
gcal_view_set_date (GCAL_VIEW (self), g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_CONTEXT:
|
|
self->context = g_value_dup_object (value);
|
|
|
|
g_signal_connect_object (gcal_context_get_clock (self->context),
|
|
"day-changed",
|
|
G_CALLBACK (gtk_widget_queue_draw),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
|
|
g_signal_connect_object (gcal_context_get_weather_service (self->context),
|
|
"weather-changed",
|
|
G_CALLBACK (weather_changed),
|
|
self,
|
|
0);
|
|
update_weather (self);
|
|
|
|
g_object_notify (object, "context");
|
|
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 void
|
|
gcal_year_view_component_added (ECalDataModelSubscriber *subscriber,
|
|
ECalClient *client,
|
|
ECalComponent *comp)
|
|
{
|
|
g_autoptr (GcalEvent) event = NULL;
|
|
GcalCalendar *calendar;
|
|
GcalYearView *self;
|
|
GDateTime *event_start, *event_end;
|
|
GError *error;
|
|
guint i, start_month, end_month;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
error = NULL;
|
|
self = GCAL_YEAR_VIEW (subscriber);
|
|
calendar = gcal_manager_get_calendar_from_source (gcal_context_get_manager (self->context),
|
|
e_client_get_source (E_CLIENT (client)));
|
|
event = gcal_event_new (calendar, comp, &error);
|
|
|
|
if (error)
|
|
{
|
|
g_warning ("Error creating event: %s", error->message);
|
|
g_clear_error (&error);
|
|
GCAL_RETURN ();
|
|
}
|
|
|
|
g_debug ("Caching event '%s' in Year view", gcal_event_get_uid (event));
|
|
|
|
event_start = gcal_event_get_date_start (event);
|
|
event_end = gcal_event_get_date_end (event);
|
|
|
|
/* Calculate the start & end months */
|
|
start_month = g_date_time_get_month (event_start) - 1;
|
|
end_month = g_date_time_get_month (event_end) - 1;
|
|
|
|
if (g_date_time_get_year (event_start) < g_date_time_get_year (self->date))
|
|
start_month = 0;
|
|
|
|
if (g_date_time_get_year (event_end) > g_date_time_get_year (self->date))
|
|
end_month = 11;
|
|
|
|
/* Add the event to the cache */
|
|
for (i = start_month; i <= end_month; i++)
|
|
g_ptr_array_add (self->events[i], g_object_ref (event));
|
|
|
|
update_sidebar (self);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self->navigator));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_component_removed (ECalDataModelSubscriber *subscriber,
|
|
ECalClient *client,
|
|
const gchar *uid,
|
|
const gchar *rid)
|
|
{
|
|
GcalYearView *year_view;
|
|
GList *children, *l;
|
|
ESource *source;
|
|
g_autofree gchar *uuid = NULL;
|
|
guint i;
|
|
gint number_of_children;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
year_view = GCAL_YEAR_VIEW (subscriber);
|
|
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));
|
|
number_of_children = g_list_length (children);
|
|
|
|
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 (g_strcmp0 (uuid, gcal_event_get_uid (event)) == 0)
|
|
{
|
|
gtk_widget_destroy (GTK_WIDGET (l->data));
|
|
number_of_children--;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* No children left visible, all the events were removed and now we have to show the
|
|
* 'No Events' placeholder.
|
|
*/
|
|
if (number_of_children == 0)
|
|
{
|
|
update_no_events_page (GCAL_YEAR_VIEW (subscriber));
|
|
gtk_stack_set_visible_child_name (GTK_STACK (year_view->navigator_stack), "no-events");
|
|
}
|
|
|
|
/* Also remove from the cached list of events */
|
|
for (i = 0; i < 12; i++)
|
|
{
|
|
GPtrArray *events;
|
|
guint j;
|
|
|
|
events = year_view->events[i];
|
|
|
|
for (j = 0; j < events->len; j++)
|
|
{
|
|
GcalEvent *event;
|
|
|
|
event = g_ptr_array_index (events, j);
|
|
|
|
if (!g_str_equal (gcal_event_get_uid (event), uuid))
|
|
continue;
|
|
|
|
g_debug ("Removing event '%s' from Year view's cache", uuid);
|
|
g_ptr_array_remove (events, event);
|
|
}
|
|
}
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (year_view->navigator));
|
|
|
|
g_list_free (children);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_component_changed (ECalDataModelSubscriber *subscriber,
|
|
ECalClient *client,
|
|
ECalComponent *comp)
|
|
{
|
|
ECalComponentId *id;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
id = e_cal_component_get_id (comp);
|
|
|
|
gcal_year_view_component_removed (subscriber, client, id->uid, id->rid);
|
|
gcal_year_view_component_added (subscriber, client, comp);
|
|
|
|
g_clear_pointer (&id, e_cal_component_free_id);
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
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_override_property (object_class, PROP_CONTEXT, "context");
|
|
|
|
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));
|
|
|
|
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_child (widget_class, GcalYearView, scrolled_window);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalYearView, temp_label);
|
|
gtk_widget_class_bind_template_child (widget_class, GcalYearView, weather_icon);
|
|
|
|
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_edge_overshot_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, navigator_motion_notify_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, navigator_scroll_event_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)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < 12; i++)
|
|
self->events[i] = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
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->first_weekday = get_first_weekday ();
|
|
|
|
self->navigator_grid = g_new0 (GridData, 1);
|
|
self->selected_data = g_new0 (ButtonData, 1);
|
|
|
|
/* 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_MOVE);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void
|
|
update_weather (GcalYearView *self)
|
|
{
|
|
GcalWeatherService *weather_service;
|
|
gboolean updated = FALSE;
|
|
|
|
g_return_if_fail (GCAL_IS_YEAR_VIEW (self));
|
|
|
|
weather_service = gcal_context_get_weather_service (self->context);
|
|
|
|
if (self->date)
|
|
{
|
|
GPtrArray *weather_infos;
|
|
GDate date;
|
|
guint i;
|
|
|
|
g_date_set_dmy (&date,
|
|
g_date_time_get_day_of_month (self->date),
|
|
g_date_time_get_month (self->date),
|
|
g_date_time_get_year (self->date));
|
|
|
|
weather_infos = gcal_weather_service_get_weather_infos (weather_service);
|
|
|
|
for (i = 0; weather_infos && i < weather_infos->len; i++)
|
|
{
|
|
GcalWeatherInfo *info;
|
|
GDate wdate;
|
|
|
|
info = g_ptr_array_index (weather_infos, i);
|
|
|
|
gcal_weather_info_get_date (info, &wdate);
|
|
|
|
if (g_date_compare (&date, &wdate) == 0)
|
|
{
|
|
const gchar *temp_str; /* unowned */
|
|
const gchar *icon_nm; /* unowned */
|
|
|
|
temp_str = gcal_weather_info_get_temperature (info);
|
|
icon_nm = gcal_weather_info_get_icon_name (info);
|
|
|
|
gtk_label_set_text (self->temp_label, temp_str);
|
|
gtk_image_set_from_icon_name (self->weather_icon, icon_nm, GTK_ICON_SIZE_SMALL_TOOLBAR);
|
|
updated = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!updated)
|
|
{
|
|
gtk_label_set_text (self->temp_label, "");
|
|
gtk_image_clear (self->weather_icon);
|
|
}
|
|
}
|
|
|