2164 lines
72 KiB
C
2164 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-timeline-subscriber.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 update_weather (GcalYearView *self);
|
|
|
|
static void gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *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 (GCAL_TYPE_TIMELINE_SUBSCRIBER,
|
|
gcal_timeline_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;
|
|
g_autofree gchar *title = NULL;
|
|
gboolean has_range;
|
|
|
|
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);
|
|
}
|
|
|
|
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 GPtrArray*
|
|
get_events_for_range (GcalYearView *self,
|
|
GDateTime *start,
|
|
GDateTime *end)
|
|
{
|
|
g_autoptr (GPtrArray) events = NULL;
|
|
guint i;
|
|
|
|
events = g_ptr_array_sized_new (50);
|
|
|
|
for (i = g_date_time_get_month (start) - 1; i <= g_date_time_get_month (end) - 1; i++)
|
|
{
|
|
GPtrArray *month_events = self->events[i];
|
|
guint j;
|
|
|
|
for (j = 0; j < month_events->len; j++)
|
|
{
|
|
GDateTime *event_start;
|
|
GDateTime *event_end;
|
|
GcalEvent *event;
|
|
|
|
event = g_ptr_array_index (month_events, j);
|
|
event_start = gcal_event_get_date_start (event);
|
|
event_end = gcal_event_get_date_end (event);
|
|
|
|
if (gcal_date_time_compare_date (event_end, start) < 0 ||
|
|
gcal_date_time_compare_date (event_start, end) > 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
g_ptr_array_add (events, event);
|
|
}
|
|
}
|
|
|
|
return g_steal_pointer (&events);
|
|
}
|
|
|
|
static void
|
|
update_sidebar (GcalYearView *year_view)
|
|
{
|
|
g_autoptr (GPtrArray) events = NULL;
|
|
GtkWidget *child_widget;
|
|
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);
|
|
|
|
events = get_events_for_range (year_view,
|
|
year_view->start_selected_date,
|
|
year_view->end_selected_date);
|
|
|
|
|
|
if (events->len == 0)
|
|
{
|
|
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 (i = 0; i < events->len; i++)
|
|
{
|
|
add_event_to_day_array (year_view,
|
|
g_ptr_array_index (events, i),
|
|
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];
|
|
GList *l;
|
|
|
|
for (l = current_day; l != NULL; l = g_list_next (l))
|
|
{
|
|
child_widget = l->data;
|
|
gtk_widget_show (child_widget);
|
|
g_signal_connect_object (child_widget, "activate", G_CALLBACK (event_activated), year_view, 0);
|
|
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_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
|
|
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)
|
|
{
|
|
g_autoptr (GcalEvent) changed_event = NULL;
|
|
GcalRecurrenceModType mod;
|
|
GcalEventWidget *event_widget;
|
|
GcalCalendar *calendar;
|
|
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);
|
|
changed_event = gcal_event_new_from_event (event);
|
|
calendar = gcal_event_get_calendar (changed_event);
|
|
mod = GCAL_RECURRENCE_MOD_THIS_ONLY;
|
|
|
|
if (gcal_event_has_recurrence (changed_event) &&
|
|
!ask_recurrence_modification_type (GTK_WIDGET (self), &mod, calendar))
|
|
{
|
|
goto out;
|
|
}
|
|
|
|
start_dt = gcal_event_get_date_start (changed_event);
|
|
end_dt = gcal_event_get_date_end (changed_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 (changed_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 (changed_event, new_end);
|
|
|
|
g_clear_pointer (&new_end, g_date_time_unref);
|
|
}
|
|
|
|
gcal_manager_update_event (gcal_context_get_manager (self->context),
|
|
changed_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);
|
|
}
|
|
|
|
/*
|
|
* GcalTimelineSubscriber implementation
|
|
*/
|
|
|
|
static GcalRange*
|
|
gcal_year_view_get_range (GcalTimelineSubscriber *subscriber)
|
|
{
|
|
g_autoptr (GDateTime) start = NULL;
|
|
g_autoptr (GDateTime) end = NULL;
|
|
GcalYearView *self;
|
|
|
|
self = GCAL_YEAR_VIEW (subscriber);
|
|
start = g_date_time_new_local (g_date_time_get_year (self->date), 1, 1, 0, 0, 0);
|
|
end = g_date_time_add_years (start, 1);
|
|
|
|
return gcal_range_new (start, end, GCAL_RANGE_DEFAULT);
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_add_event (GcalTimelineSubscriber *subscriber,
|
|
GcalEvent *event)
|
|
{
|
|
GcalYearView *self;
|
|
GDateTime *event_start;
|
|
GDateTime *event_end;
|
|
guint start_month;
|
|
guint end_month;
|
|
guint i;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
self = GCAL_YEAR_VIEW (subscriber);
|
|
|
|
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_remove_event (GcalTimelineSubscriber *subscriber,
|
|
GcalEvent *event)
|
|
{
|
|
g_autoptr (GList) children = NULL;
|
|
GcalYearView *self;
|
|
GList *l;
|
|
const gchar *event_id;
|
|
guint n_children;
|
|
guint i;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
self = GCAL_YEAR_VIEW (subscriber);
|
|
event_id = gcal_event_get_uid (event);
|
|
|
|
n_children = 0;
|
|
children = gtk_container_get_children (GTK_CONTAINER (self->events_sidebar));
|
|
|
|
for (l = children; l != NULL; l = g_list_next (l))
|
|
{
|
|
GcalEventWidget *child_widget;
|
|
GcalEvent *list_event;
|
|
|
|
child_widget = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (l->data)));
|
|
list_event = gcal_event_widget_get_event (child_widget);
|
|
|
|
if (g_strcmp0 (event_id, gcal_event_get_uid (list_event)) == 0)
|
|
gtk_widget_destroy (GTK_WIDGET (l->data));
|
|
else
|
|
n_children++;
|
|
}
|
|
|
|
/*
|
|
* No children left visible, all the events were removed and now we have to show the
|
|
* 'No Events' placeholder.
|
|
*/
|
|
if (n_children == 0)
|
|
{
|
|
update_no_events_page (self);
|
|
gtk_stack_set_visible_child_name (GTK_STACK (self->navigator_stack), "no-events");
|
|
}
|
|
|
|
/* Also remove from the cached list of events */
|
|
for (i = 0; i < 12; i++)
|
|
{
|
|
GPtrArray *events;
|
|
guint j;
|
|
|
|
events = self->events[i];
|
|
|
|
for (j = 0; j < events->len; j++)
|
|
{
|
|
GcalEvent *cached_event;
|
|
|
|
cached_event = g_ptr_array_index (events, j);
|
|
|
|
if (!g_str_equal (gcal_event_get_uid (cached_event), event_id))
|
|
continue;
|
|
|
|
g_debug ("Removing event '%s' from Year view's cache", event_id);
|
|
g_ptr_array_remove (events, event);
|
|
}
|
|
}
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (self->navigator));
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_year_view_update_event (GcalTimelineSubscriber *subscriber,
|
|
GcalEvent *event)
|
|
{
|
|
GCAL_ENTRY;
|
|
|
|
gcal_year_view_remove_event (subscriber, event);
|
|
gcal_year_view_add_event (subscriber, event);
|
|
|
|
|
|
GCAL_EXIT;
|
|
}
|
|
|
|
static void
|
|
gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface)
|
|
{
|
|
iface->get_range = gcal_year_view_get_range;
|
|
iface->add_event = gcal_year_view_add_event;
|
|
iface->remove_event = gcal_year_view_remove_event;
|
|
iface->update_event = gcal_year_view_update_event;
|
|
}
|
|
|
|
|
|
/* 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)
|
|
{
|
|
GcalYearView *self;
|
|
gboolean year_changed;
|
|
gboolean needs_reset;
|
|
|
|
GCAL_ENTRY;
|
|
|
|
self = GCAL_YEAR_VIEW (view);
|
|
|
|
needs_reset = self->date &&
|
|
self->start_selected_date &&
|
|
!g_date_time_equal (self->date, date);
|
|
|
|
year_changed = !self->date || g_date_time_get_year (self->date) != g_date_time_get_year (date);
|
|
|
|
gcal_set_date_time (&self->date, date);
|
|
|
|
self->first_week_of_year = get_last_week_of_year_dmy (self->first_weekday,
|
|
1,
|
|
G_DATE_JANUARY,
|
|
g_date_time_get_year (self->date));
|
|
self->last_week_of_year = get_last_week_of_year_dmy (self->first_weekday,
|
|
31,
|
|
G_DATE_DECEMBER,
|
|
g_date_time_get_year (self->date));
|
|
|
|
if (needs_reset)
|
|
reset_sidebar (self);
|
|
|
|
if (year_changed)
|
|
gcal_timeline_subscriber_range_changed (GCAL_TIMELINE_SUBSCRIBER (view));
|
|
|
|
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 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)
|
|
{
|
|
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->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_clear_pointer (&year_view->start_selected_date, g_date_time_unref);
|
|
g_clear_pointer (&year_view->end_selected_date, g_date_time_unref);
|
|
g_clear_pointer (&year_view->date, g_date_time_unref);
|
|
|
|
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:
|
|
g_assert (self->context == NULL);
|
|
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);
|
|
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_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/ui/views/gcal-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_object (self->calendar_settings, "changed::show-weekdate", G_CALLBACK (gtk_widget_queue_draw), self, G_CONNECT_SWAPPED);
|
|
|
|
/* 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
|
|
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);
|
|
}
|
|
}
|
|
|