Files
federated-calendar/src/gui/views/gcal-month-cell.c
T
Philipp Sauberzweig ef4c70fa4c month-cell: Add pill style to first day of the month.
Add a pill style to the label of the first day of the month to make it easy to identify. Adjust the border radius of the existing pill style for the current date to be the same. Apply the font-weight using the new style class.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/647>
2025-10-28 14:06:44 +00:00

640 lines
18 KiB
C

/* gcal-month-cell.c
*
* Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@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 "GcalMonthCell"
#include "config.h"
#include "gcal-application.h"
#include "gcal-clock.h"
#include "gcal-debug.h"
#include "gcal-event-widget.h"
#include "gcal-month-cell.h"
#include "gcal-utils.h"
#include "gcal-weather-info.h"
struct _GcalMonthCell
{
AdwBreakpointBin parent;
GDateTime *date;
guint n_overflow;
AdwBreakpoint *breakpoint_narrow;
GtkLabel *day_label;
GtkWidget *header_box;
GtkImage *weather_icon;
GtkLabel *temp_label;
GtkWidget *overflow_button;
GtkInscription *overflow_inscription;
GtkWidget *overlay;
gboolean different_month;
gboolean selected;
GcalContext *context;
GcalWeatherInfo *weather_info;
GBinding *icon_tooltip_binding;
};
G_DEFINE_TYPE (GcalMonthCell, gcal_month_cell, ADW_TYPE_BREAKPOINT_BIN)
enum
{
SHOW_OVERFLOW,
N_SIGNALS
};
enum
{
PROP_0,
PROP_CONTEXT,
N_PROPS
};
static guint signals[N_SIGNALS] = { 0, };
static GParamSpec *properties[N_PROPS] = { 0, };
/*
* Auxiliary methods
*/
static void
update_style_flags (GcalMonthCell *self)
{
g_autoptr (GDateTime) today = NULL;
gint weekday;
/* Today */
today = g_date_time_new_now_local ();
if (gcal_date_time_compare_date (self->date, today) == 0)
gtk_widget_add_css_class (GTK_WIDGET (self), "today");
else
gtk_widget_remove_css_class (GTK_WIDGET (self), "today");
weekday = g_date_time_get_day_of_week (self->date);
if (is_workday (weekday))
gtk_widget_add_css_class (GTK_WIDGET (self), "workday");
else
gtk_widget_remove_css_class (GTK_WIDGET (self), "workday");
}
static void
add_month_separators (GcalMonthCell *self)
{
gint day_of_month;
gtk_widget_remove_css_class (GTK_WIDGET (self), "separator-top");
gtk_widget_remove_css_class (GTK_WIDGET (self), "separator-side");
day_of_month = g_date_time_get_day_of_month (self->date);
if (day_of_month > 1 && day_of_month <= 7)
{
gtk_widget_add_css_class (GTK_WIDGET (self), "separator-top");
}
else if (day_of_month == 1)
{
gtk_widget_add_css_class (GTK_WIDGET (self), "separator-side");
gtk_widget_add_css_class (GTK_WIDGET (self), "separator-top");
}
}
static void
move_event (GcalMonthCell *self,
GcalEvent *event,
GcalRecurrenceModType mod_type)
{
g_autoptr (GcalEvent) changed_event = NULL;
g_autoptr (GDateTime) start_dt = NULL;
GTimeSpan timespan = 0;
GDateTime *end_dt;
gint diff;
gint start_month, current_month;
gint start_year, current_year;
GCAL_ENTRY;
changed_event = gcal_event_new_from_event (event);
/* Move the event's date */
start_dt = gcal_event_get_date_start (changed_event);
end_dt = gcal_event_get_date_end (changed_event);
start_month = g_date_time_get_month (start_dt);
start_year = g_date_time_get_year (start_dt);
current_month = g_date_time_get_month (self->date);
current_year = g_date_time_get_year (self->date);
timespan = g_date_time_difference (end_dt, start_dt);
start_dt = g_date_time_add_full (start_dt,
current_year - start_year,
current_month - start_month,
0, 0, 0, 0);
diff = gcal_date_time_compare_date (self->date, start_dt);
if (diff != 0)
{
g_autoptr (GDateTime) new_start = g_date_time_add_days (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)
{
g_autoptr (GDateTime) new_end = g_date_time_add (new_start, timespan);
gcal_event_set_date_end (changed_event, new_end);
}
gcal_manager_update_event (gcal_context_get_manager (self->context), changed_event, mod_type);
}
}
static void
update_weather (GcalMonthCell *self)
{
GcalWeatherService *weather_service;
GcalWeatherInfo *weather_info;
GDate date;
gint day_of_month;
if (!self->context)
return;
day_of_month = g_date_time_get_day_of_month (self->date);
g_date_set_dmy (&date,
day_of_month,
g_date_time_get_month (self->date),
g_date_time_get_year (self->date));
weather_service = gcal_context_get_weather_service (self->context);
weather_info = gcal_weather_service_get_weather_info_for_date (weather_service, &date);
g_assert (!weather_info || GCAL_IS_WEATHER_INFO (weather_info));
if (self->weather_info == weather_info)
return;
self->weather_info = weather_info;
if (weather_info)
{
const gchar *icon_name; /* unowned */
const gchar *temp_str; /* unowned */
icon_name = gcal_weather_info_get_icon_name (weather_info);
temp_str = gcal_weather_info_get_temperature (weather_info);
gtk_image_set_from_icon_name (self->weather_icon, icon_name);
gtk_label_set_text (self->temp_label, temp_str);
/* Use a short month name label to avoid conflicting with the weather forecast's labels */
if (day_of_month == 1)
{
g_autofree gchar *month_name = g_date_time_format (self->date, "%b");
gtk_label_set_text (self->day_label, month_name);
}
}
else
{
gtk_image_clear (self->weather_icon);
gtk_label_set_text (self->temp_label, "");
/* No risk of conflicting with the weather forecast labels in their absence, use the full month name label */
if (day_of_month == 1)
{
g_autofree gchar *month_name = g_date_time_format (self->date, "%B");
gtk_label_set_text (self->day_label, month_name);
}
}
}
/*
* Callbacks
*/
static void
day_changed_cb (GcalClock *clock,
GcalMonthCell *self)
{
update_style_flags (self);
}
static void
overflow_button_clicked_cb (GtkWidget *button,
GcalMonthCell *self)
{
g_signal_emit (self, signals[SHOW_OVERFLOW], 0, button);
}
static gboolean
on_drop_target_accept_cb (GtkDropTarget *drop_target,
GdkDrop *drop,
GcalMonthCell *self)
{
GCAL_ENTRY;
if ((gdk_drop_get_actions (drop) & gtk_drop_target_get_actions (drop_target)) == 0)
GCAL_RETURN (FALSE);
if (!gdk_content_formats_contain_gtype (gdk_drop_get_formats (drop), GCAL_TYPE_EVENT_WIDGET))
GCAL_RETURN (FALSE);
GCAL_RETURN (TRUE);
}
static void
on_ask_recurrence_response_cb (GcalEvent *event,
GcalRecurrenceModType mod_type,
gpointer user_data)
{
GcalMonthCell *self = GCAL_MONTH_CELL (user_data);
if (mod_type != GCAL_RECURRENCE_MOD_NONE)
move_event (self, event, mod_type);
}
static gboolean
on_drop_target_drop_cb (GtkDropTarget *drop_target,
const GValue *value,
gdouble x,
gdouble y,
GcalMonthCell *self)
{
GcalEventWidget *event_widget;
GcalEvent *event;
GCAL_ENTRY;
if (!G_VALUE_HOLDS (value, GCAL_TYPE_EVENT_WIDGET))
GCAL_RETURN (FALSE);
event_widget = g_value_get_object (value);
event = gcal_event_widget_get_event (event_widget);
if (gcal_event_has_recurrence (event))
{
gcal_utils_ask_recurrence_modification_type (GTK_WIDGET (self),
event,
FALSE,
on_ask_recurrence_response_cb,
self);
}
else
{
move_event (self, event, GCAL_RECURRENCE_MOD_THIS_ONLY);
}
GCAL_RETURN (TRUE);
}
static void
on_weather_service_weather_changed_cb (GcalWeatherService *weather_service,
GcalMonthCell *self)
{
update_weather (self);
}
static void
on_breakpoint_changed_cb (GcalMonthCell *self,
GParamSpec *pspec)
{
AdwBreakpoint *current_breakpoint = adw_breakpoint_bin_get_current_breakpoint (ADW_BREAKPOINT_BIN (self));
if (current_breakpoint == self->breakpoint_narrow)
{
self->icon_tooltip_binding = g_object_bind_property (self->temp_label, "label",
self->weather_icon, "tooltip-text",
G_BINDING_SYNC_CREATE);
}
else
{
g_clear_pointer (&self->icon_tooltip_binding, g_binding_unbind);
gtk_widget_set_tooltip_text (GTK_WIDGET (self->weather_icon), NULL);
}
}
/*
* GObject overrides
*/
static void
gcal_month_cell_dispose (GObject *object)
{
GcalMonthCell *self = (GcalMonthCell *)object;
GcalWeatherService *weather_service = gcal_context_get_weather_service (self->context);
gcal_weather_service_release (weather_service);
gcal_clear_date_time (&self->date);
g_clear_object (&self->context);
G_OBJECT_CLASS (gcal_month_cell_parent_class)->dispose (object);
}
static void
gcal_month_cell_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GcalMonthCell *self = (GcalMonthCell *) object;
switch (property_id)
{
case PROP_CONTEXT:
gcal_month_cell_set_context (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gcal_month_cell_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GcalMonthCell *self = (GcalMonthCell *) object;
switch (property_id)
{
case PROP_CONTEXT:
g_value_set_object (value, self->context);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gcal_month_cell_class_init (GcalMonthCellClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = gcal_month_cell_dispose;
object_class->set_property = gcal_month_cell_set_property;
object_class->get_property = gcal_month_cell_get_property;
signals[SHOW_OVERFLOW] = g_signal_new ("show-overflow",
GCAL_TYPE_MONTH_CELL,
G_SIGNAL_RUN_LAST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
1,
GTK_TYPE_WIDGET);
properties[PROP_CONTEXT] = g_param_spec_object ("context",
"Context",
"The GcalContext of the application",
GCAL_TYPE_CONTEXT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/views/gcal-month-cell.ui");
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, breakpoint_narrow);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, day_label);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, header_box);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overflow_button);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overflow_inscription);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overlay);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, temp_label);
gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, weather_icon);
gtk_widget_class_bind_template_callback (widget_class, overflow_button_clicked_cb);
gtk_widget_class_set_css_name (widget_class, "monthcell");
}
static void
gcal_month_cell_init (GcalMonthCell *self)
{
GtkDropTarget *drop_target;
gtk_widget_init_template (GTK_WIDGET (self));
drop_target = gtk_drop_target_new (GCAL_TYPE_EVENT_WIDGET, GDK_ACTION_COPY);
g_signal_connect (drop_target, "accept", G_CALLBACK (on_drop_target_accept_cb), self);
g_signal_connect (drop_target, "drop", G_CALLBACK (on_drop_target_drop_cb), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
g_signal_connect(self, "notify::current-breakpoint", G_CALLBACK (on_breakpoint_changed_cb), 0);
}
GtkWidget*
gcal_month_cell_new (void)
{
return g_object_new (GCAL_TYPE_MONTH_CELL, NULL);
}
GDateTime*
gcal_month_cell_get_date (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), NULL);
return self->date;
}
void
gcal_month_cell_set_date (GcalMonthCell *self,
GDateTime *date)
{
gint day_of_month;
g_return_if_fail (GCAL_IS_MONTH_CELL (self));
if (self->date && date && gcal_date_time_compare_date (self->date, date) == 0)
return;
gcal_clear_date_time (&self->date);
self->date = g_date_time_ref (date);
day_of_month = g_date_time_get_day_of_month (date);
update_style_flags (self);
add_month_separators (self);
if (day_of_month == 1)
{
g_autofree gchar *month_name = g_date_time_format (date, "%B");
gtk_label_set_text (self->day_label, month_name);
gtk_widget_add_css_class (GTK_WIDGET (self->day_label), "first-day");
}
else
{
g_autofree gchar *date_number = g_strdup_printf ("%d", day_of_month);
gtk_label_set_text (self->day_label, date_number);
gtk_widget_remove_css_class (GTK_WIDGET (self->day_label), "first-day");
}
update_weather (self);
}
GcalContext*
gcal_month_cell_get_context (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), NULL);
return self->context;
}
void
gcal_month_cell_set_context (GcalMonthCell *self,
GcalContext *context)
{
g_return_if_fail (GCAL_IS_MONTH_CELL (self));
if (!g_set_object (&self->context, context))
return;
g_signal_connect_object (gcal_context_get_clock (context),
"day-changed",
G_CALLBACK (day_changed_cb),
self,
0);
g_signal_connect_object (gcal_context_get_weather_service (self->context),
"weather-changed",
G_CALLBACK (on_weather_service_weather_changed_cb),
self,
0);
GcalWeatherService *weather_service = gcal_context_get_weather_service (self->context);
gcal_weather_service_hold (weather_service);
update_weather (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CONTEXT]);
}
guint
gcal_month_cell_get_overflow (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), 0);
return self->n_overflow;
}
void
gcal_month_cell_set_overflow (GcalMonthCell *self,
guint n_overflow)
{
g_return_if_fail (GCAL_IS_MONTH_CELL (self));
if (self->n_overflow == n_overflow)
return;
self->n_overflow = n_overflow;
gtk_widget_set_sensitive (self->overflow_button, n_overflow > 0);
if (n_overflow > 0)
{
g_autofree gchar *text = g_strdup_printf ("+%d", n_overflow);
gtk_inscription_set_text (self->overflow_inscription, text);
}
else
{
gtk_inscription_set_text (self->overflow_inscription, "");
}
}
gint
gcal_month_cell_get_content_space (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), -1);
return gtk_widget_get_height (GTK_WIDGET (self)) -
gcal_month_cell_get_header_height (self);
}
gint
gcal_month_cell_get_header_height (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), -1);
return gtk_widget_get_height (self->header_box) +
gtk_widget_get_margin_top (self->header_box) +
gtk_widget_get_margin_bottom (self->header_box);
}
gint
gcal_month_cell_get_overflow_height (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), -1);
return gtk_widget_get_height (self->overflow_button);
}
gboolean
gcal_month_cell_get_selected (GcalMonthCell *self)
{
g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), FALSE);
return self->selected;
}
void
gcal_month_cell_set_selected (GcalMonthCell *self,
gboolean selected)
{
GtkStateFlags flags;
g_return_if_fail (GCAL_IS_MONTH_CELL (self));
if (self->selected == selected)
return;
self->selected = selected;
/* Update style context state */
flags = gtk_widget_get_state_flags (GTK_WIDGET (self));
if (selected)
flags |= GTK_STATE_FLAG_SELECTED;
else
flags &= ~GTK_STATE_FLAG_SELECTED;
gtk_widget_set_state_flags (GTK_WIDGET (self), flags, TRUE);
gtk_widget_queue_resize (GTK_WIDGET (self));
}