Files
federated-calendar/src/gui/common/gcal-date-chooser.c
T
Hari Rana dfb9ea645c common/date-chooser: Use GcalGizmo
GcalGizmo allows us to handle focus within child widgets
much more cleanly, as it no longer requires us to override the
ancestor and re-iterating through children.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-calendar/-/merge_requests/760>
2026-09-16 12:22:36 +00:00

1370 lines
40 KiB
C

/* GTK - The GIMP Toolkit
*
* Copyright (C) 2015 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#define G_LOG_DOMAIN "GcalDateChooser"
#include "gcal-utils.h"
#include "gcal-date-chooser.h"
#include "gcal-gizmo.h"
#include "gcal-date-chooser-day.h"
#include "gcal-multi-choice.h"
#include "gcal-range-tree.h"
#include "gcal-timeline-subscriber.h"
#include "gcal-view.h"
#include <glib/gi18n.h>
#include <langinfo.h>
#include <locale.h>
#include <stdlib.h>
#define ROWS 6
#define DAYS ROWS * N_WEEKDAYS
struct _GcalDateChooser
{
AdwBin parent;
GtkWidget *month_choice;
GtkWidget *popover_month_choice;
GtkWidget *year_choice;
GtkWidget *combined_choice;
GcalGizmo *gizmo;
GtkWidget *grid;
GtkWidget *day_grid;
GtkWidget *corner;
GtkWidget *cols[N_WEEKDAYS];
GtkWidget *rows[ROWS];
GtkWidget *days[ROWS][N_WEEKDAYS];
GtkWidget *week[ROWS];
GDateTime *date;
GSignalGroup *model_signal_group;
gint this_year;
gint week_start;
gboolean show_heading;
gboolean show_day_names;
gboolean show_week_numbers;
gboolean show_selected_week;
gboolean show_events;
gboolean split_month_year;
unsigned int update_indicators_idle_id;
};
static void gcal_view_interface_init (GcalViewInterface *iface);
static void gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface);
static gboolean update_event_indicators_in_idle_cb (gpointer data);
static void gcal_date_chooser_set_date (GcalView *view,
GDateTime *date);
G_DEFINE_TYPE_WITH_CODE (GcalDateChooser, gcal_date_chooser, ADW_TYPE_BIN,
G_IMPLEMENT_INTERFACE (GCAL_TYPE_VIEW, gcal_view_interface_init)
G_IMPLEMENT_INTERFACE (GCAL_TYPE_TIMELINE_SUBSCRIBER,
gcal_timeline_subscriber_interface_init));
enum
{
DAY_SELECTED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_SHOW_HEADING,
PROP_SHOW_DAY_NAMES,
PROP_SHOW_WEEK_NUMBERS,
PROP_SHOW_SELECTED_WEEK,
PROP_SHOW_EVENTS,
PROP_SPLIT_MONTH_YEAR,
PROP_DATE,
PROP_TIME_DIRECTION,
NUM_PROPERTIES = PROP_SPLIT_MONTH_YEAR + 1,
};
static guint signals[LAST_SIGNAL] = { 0, };
static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static const guint month_length[2][13] =
{
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
static gboolean
get_combined_choice_visible (gpointer user_data,
gboolean show_heading,
gboolean split_month_year)
{
return show_heading && !split_month_year;
}
static gboolean
get_split_choice_visible (gpointer user_data,
gboolean show_heading,
gboolean split_month_year)
{
return show_heading && split_month_year;
}
static void
decode_combined_choice_value (gint value,
gint *year,
gint *month,
gint *day)
{
gint y, m, d, ym;
d = value % 32 + 1;
ym = value / 32;
y = ym / 12;
m = ym % 12 + 1;
g_assert (g_date_valid_dmy (d, m, y));
if (year)
*year = y;
if (month)
*month = m;
if (day)
*day = d;
}
static gint
encode_combined_choice_value (gint year,
gint month,
gint day)
{
g_assert (g_date_valid_dmy (day, month, year));
return (year * 12 + month - 1) * 32 + day - 1;
}
static gint
compute_prev_combined_choice_value (gint value)
{
gint year, month;
decode_combined_choice_value (value, &year, &month, NULL);
if (--month < 1)
{
month = 12;
year--;
}
return encode_combined_choice_value (year, month, 1);
}
static gint
compute_next_combined_choice_value (gint value)
{
gint year, month;
decode_combined_choice_value (value, &year, &month, NULL);
if (++month > 12)
{
month = 1;
year++;
}
return encode_combined_choice_value (year, month, 1);
}
static gboolean
leap (guint year)
{
return ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0));
}
static void
calendar_compute_days (GcalDateChooser *self)
{
GcalDateChooserDay *d;
GDateTime *date;
gint year, month, day;
gint other_year, other_month;
gint ndays_in_month;
gint ndays_in_prev_month;
gint first_day;
gint row;
gint col;
g_date_time_get_ymd (self->date, &year, &month, NULL);
ndays_in_month = month_length[leap (year)][month];
date = g_date_time_new_local (year, month, 1, 0, 0, 0);
first_day = g_date_time_get_day_of_week (date);
g_date_time_unref (date);
first_day = (first_day + N_WEEKDAYS - self->week_start) % N_WEEKDAYS;
if (first_day == 0)
first_day = N_WEEKDAYS;
/* Compute days of previous month */
if (month > 1)
ndays_in_prev_month = month_length[leap (year)][month - 1];
else
ndays_in_prev_month = month_length[leap (year - 1)][12];
day = ndays_in_prev_month - first_day + 1;
other_year = year;
other_month = month - 1;
if (other_month == 0)
{
other_month = 12;
other_year -= 1;
}
for (col = 0; col < first_day; col++)
{
d = GCAL_DATE_CHOOSER_DAY (self->days[0][col]);
date = g_date_time_new_local (other_year, other_month, day, 0, 0, 0);
gcal_date_chooser_day_set_date (d, date);
gcal_date_chooser_day_set_other_month (d, TRUE);
g_date_time_unref (date);
day++;
}
/* Compute days of current month */
row = first_day / N_WEEKDAYS;
col = first_day % N_WEEKDAYS;
for (day = 1; day <= ndays_in_month; day++)
{
d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]);
date = g_date_time_new_local (year, month, day, 0, 0, 0);
gcal_date_chooser_day_set_date (d, date);
gcal_date_chooser_day_set_other_month (d, FALSE);
g_date_time_unref (date);
col++;
if (col == N_WEEKDAYS)
{
row++;
col = 0;
}
}
/* Compute days of next month */
other_year = year;
other_month = month + 1;
if (other_month == 13)
{
other_month = 1;
other_year += 1;
}
day = 1;
for (; row < ROWS; row++)
{
for (; col < N_WEEKDAYS; col++)
{
d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]);
date = g_date_time_new_local (other_year, other_month, day, 0, 0, 0);
gcal_date_chooser_day_set_date (d, date);
gcal_date_chooser_day_set_other_month (d, TRUE);
g_date_time_unref (date);
day++;
}
col = 0;
}
/* update week numbers */
for (row = 0; row < ROWS; row++)
{
gchar *text;
d = GCAL_DATE_CHOOSER_DAY (self->days[row][N_WEEKDAYS - 1]);
date = gcal_date_chooser_day_get_date (d);
text = g_strdup_printf ("%d", g_date_time_get_week_of_year (date));
gtk_label_set_label (GTK_LABEL (self->rows[row]), text);
g_free (text);
}
}
/* 0 == sunday */
static const char *
calendar_get_weekday_name (gint i)
{
const char *locale = g_getenv ("LC_TIME");
const char *res;
locale_t old_loc;
locale_t loc = (locale_t) 0;
if (locale)
loc = newlocale (LC_MESSAGES_MASK, locale, (locale_t) 0);
old_loc = uselocale (loc);
switch (i)
{
case 0:
/* Translators: Calendar grid abbreviation for Sunday.
*
* NOTE: These grid abbreviations are always shown together
* and in order, e.g. "S M T W T F S".
*
* Please make sure these abbreviations match GNOME Shell's
* abbreviations for your language.
*/
res = C_("grid sunday", "S");
break;
case 1:
/* Translators: Calendar grid abbreviation for Monday */
res = C_("grid monday", "M");
break;
case 2:
/* Translators: Calendar grid abbreviation for Tuesday */
res = C_("grid tuesday", "T");
break;
case 3:
/* Translators: Calendar grid abbreviation for Wednesday */
res = C_("grid wednesday", "W");
break;
case 4:
/* Translators: Calendar grid abbreviation for Thursday */
res = C_("grid thursday", "T");
break;
case 5:
/* Translators: Calendar grid abbreviation for Friday */
res = C_("grid friday", "F");
break;
case 6:
/* Translators: Calendar grid abbreviation for Saturday */
res = C_("grid saturday", "S");
break;
default:
g_assert_not_reached ();
}
uselocale (old_loc);
if (loc != (locale_t) 0)
freelocale (loc);
return res;
}
static void
calendar_init_weekday_display (GcalDateChooser *self)
{
for (gint i = 0; i < N_WEEKDAYS; i++)
{
const char *text = calendar_get_weekday_name ((i + self->week_start) % N_WEEKDAYS);
gtk_label_set_label (GTK_LABEL (self->cols[i]), text);
}
}
static gchar *
format_month (GcalMultiChoice *choice,
gint value,
gpointer data)
{
return g_strdup (gcal_get_month_name (value));
}
static gchar *
format_month_year (GcalMultiChoice *choice,
gint value,
gpointer data)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (data);
g_autoptr (GDateTime) now = g_date_time_new_now (g_date_time_get_timezone (self->date));
gint year, month;
decode_combined_choice_value (value, &year, &month, NULL);
if (g_date_time_get_year (now) == year)
return g_strdup (gcal_get_month_name (month - 1));
else
return g_strdup_printf ("%s %d", gcal_get_month_name (month - 1), year);
}
static void
calendar_init_month_display (GcalDateChooser *self)
{
gchar *months[13];
gchar *month;
gint i;
for (i = 0; i < 12; i++)
{
month = gcal_get_month_name (i);
months[i] = g_strdup (month);
}
months[12] = NULL;
gcal_multi_choice_set_choices (GCAL_MULTI_CHOICE (self->month_choice),
(const gchar**) months);
gcal_multi_choice_set_choices (GCAL_MULTI_CHOICE (self->popover_month_choice),
(const gchar**) months);
for (i = 0; i < 12; i++)
g_free (months[i]);
gcal_multi_choice_set_format_callback (GCAL_MULTI_CHOICE (self->month_choice),
format_month,
self,
NULL);
gcal_multi_choice_set_format_callback (GCAL_MULTI_CHOICE (self->popover_month_choice),
format_month,
self,
NULL);
gcal_multi_choice_set_format_callback (GCAL_MULTI_CHOICE (self->combined_choice),
format_month_year,
self,
NULL);
}
static void
calendar_update_selected_day_display (GcalDateChooser *self)
{
GcalDateChooserDay *d;
GDateTime *date;
gint row, col;
for (row = 0; row < ROWS; row++)
{
gboolean row_selected = FALSE;
for (col = 0; col < N_WEEKDAYS; col++)
{
gboolean day_selected;
d = GCAL_DATE_CHOOSER_DAY (self->days[row][col]);
date = gcal_date_chooser_day_get_date (d);
day_selected = gcal_date_time_compare_date (date, self->date) == 0;
gcal_date_chooser_day_set_selected (d, day_selected);
row_selected |= day_selected;
}
gtk_widget_set_visible (self->week[row], row_selected && self->show_selected_week);
}
}
static void
calendar_update_selected_day (GcalDateChooser *self)
{
GDateTime *date;
gint month_len;
gint year, month, day;
g_date_time_get_ymd (self->date, &year, &month, &day);
month_len = month_length[leap (year)][month];
if (month_len < day)
{
date = g_date_time_new_local (year, month, month_len, 0, 0, 0);
gcal_date_chooser_set_date (GCAL_VIEW (self), date);
g_date_time_unref (date);
}
else
{
calendar_update_selected_day_display (self);
}
}
static void
update_event_indicators (GcalDateChooser *self)
{
g_autoptr (GcalRangeTree) events_at_range = NULL;
g_autoptr (GListModel) events = NULL;
gint row, col;
events = g_signal_group_dup_target (self->model_signal_group);
if (events)
{
size_t n_events = g_list_model_get_n_items (events);
events_at_range = gcal_range_tree_new ();
for (size_t i = 0; i < n_events; i++)
{
g_autoptr (GcalEvent) event = g_list_model_get_item (events, i);
gcal_range_tree_add_range (events_at_range, gcal_event_get_range (event), event);
}
}
for (row = 0; row < ROWS; row++)
{
for (col = 0; col < N_WEEKDAYS; col++)
{
GcalDateChooserDay *day = GCAL_DATE_CHOOSER_DAY (self->days[row][col]);
if (events_at_range)
{
g_autoptr (GcalRange) range = NULL;
GDateTime *date;
uint64_t n_events;
date = gcal_date_chooser_day_get_date (day);
range = gcal_range_new_take (g_date_time_ref (date),
g_date_time_add_days (date, 1),
GCAL_RANGE_DEFAULT);
n_events = gcal_range_tree_count_entries_at_range (events_at_range, range);
gcal_date_chooser_day_set_dot_visible (day, n_events > 0);
}
else
{
gcal_date_chooser_day_set_dot_visible (day, FALSE);
}
}
}
}
static void
queue_update_event_indicators (GcalDateChooser *self)
{
if (self->update_indicators_idle_id > 0)
return;
self->update_indicators_idle_id = g_idle_add_full (G_PRIORITY_LOW,
update_event_indicators_in_idle_cb,
self,
NULL);
}
static void
day_selected_cb (GcalDateChooserDay *d,
GcalDateChooser *self)
{
gcal_date_chooser_set_date (GCAL_VIEW (self), gcal_date_chooser_day_get_date (d));
}
static void
on_clock_day_changed_cb (GcalDateChooser *self)
{
/* FIXME Update the widget to the new day. */
}
static gboolean
update_event_indicators_in_idle_cb (gpointer data)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (data);
update_event_indicators (self);
self->update_indicators_idle_id = 0;
return G_SOURCE_REMOVE;
}
/*
* GcalView interface
*/
static void
gcal_date_chooser_set_date (GcalView *view,
GDateTime *date)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (view);
gint y1, m1, d1, y2, m2, d2;
g_object_freeze_notify (G_OBJECT (self));
g_date_time_get_ymd (self->date, &y1, &m1, &d1);
g_date_time_get_ymd (date, &y2, &m2, &d2);
if (y1 != y2 || m1 != m2)
d2 = 1;
gcal_set_date_time (&self->date, date);
if (y1 != y2 || m1 != m2 || d1 != d2)
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->combined_choice),
encode_combined_choice_value (y2, m2, d2));
if (y1 != y2 || m1 != m2)
{
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->year_choice), y2);
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->month_choice), m2 - 1);
calendar_compute_days (self);
gcal_timeline_subscriber_range_changed (GCAL_TIMELINE_SUBSCRIBER (self));
}
if (y1 != y2 || m1 != m2 || d1 != d2)
{
calendar_update_selected_day (self);
g_signal_emit (self, signals[DAY_SELECTED], 0);
g_object_notify (G_OBJECT (self), "active-date");
}
g_object_thaw_notify (G_OBJECT (self));
}
static GDateTime*
gcal_date_chooser_get_date (GcalView *view)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (view);
return self->date;
}
static GList*
gcal_date_chooser_get_children_by_uuid (GcalView *view,
GcalRecurrenceModType mod,
const gchar *uuid)
{
return NULL;
}
static GDateTime*
gcal_date_chooser_get_next_date (GcalView *view)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (view);
g_assert (self->date != NULL);
return g_date_time_add_months (self->date, 1);
}
static GDateTime*
gcal_date_chooser_get_previous_date (GcalView *view)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (view);
g_assert (self->date != NULL);
return g_date_time_add_months (self->date, -1);
}
static void
gcal_view_interface_init (GcalViewInterface *iface)
{
iface->get_date = gcal_date_chooser_get_date;
iface->set_date = gcal_date_chooser_set_date;
iface->get_children_by_uuid = gcal_date_chooser_get_children_by_uuid;
iface->get_next_date = gcal_date_chooser_get_next_date;
iface->get_previous_date = gcal_date_chooser_get_previous_date;
}
/*
* GcalGizmo overrides
*/
static gboolean
gcal_gizmo_focus (GcalGizmo *gizmo,
GtkDirectionType direction,
gpointer user_data)
{
GtkWidget *focus_widget, *new_focus;
GTypeClass *gizmo_parent_class;
gboolean is_tab, is_rtl;
GtkRoot *root;
is_tab = direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD;
if (gtk_widget_get_focus_child (GTK_WIDGET (gizmo)) && is_tab)
return FALSE;
if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT)
{
is_rtl = gtk_widget_get_direction (GTK_WIDGET (gizmo)) == GTK_TEXT_DIR_RTL;
root = gtk_widget_get_root (GTK_WIDGET (gizmo));
focus_widget = gtk_root_get_focus (root);
g_assert (GCAL_IS_DATE_CHOOSER_DAY (focus_widget));
if (direction == (is_rtl ? GTK_DIR_LEFT : GTK_DIR_RIGHT))
new_focus = gtk_widget_get_next_sibling (focus_widget);
else
new_focus = gtk_widget_get_prev_sibling (focus_widget);
if (new_focus)
return gtk_widget_grab_focus (new_focus);
}
gizmo_parent_class = g_type_class_peek_parent (G_OBJECT_GET_CLASS (gizmo));
if (!GTK_WIDGET_CLASS (gizmo_parent_class)->focus (GTK_WIDGET (gizmo), direction))
return gtk_widget_keynav_failed (GTK_WIDGET (gizmo), direction);
return TRUE;
}
/*
* GcalTimelineSubscriber implementation
*/
static GcalRange*
gcal_date_chooser_get_range (GcalTimelineSubscriber *subscriber)
{
GcalDateChooser *self;
GDateTime *start;
g_autoptr (GDateTime) end = NULL;
self = GCAL_DATE_CHOOSER (subscriber);
start = gcal_date_chooser_day_get_date (GCAL_DATE_CHOOSER_DAY (self->days[0][0]));
end = g_date_time_add_days (start, G_N_ELEMENTS (self->days) * G_N_ELEMENTS (self->days[0]));
return gcal_range_new (start, end, GCAL_RANGE_DEFAULT);
}
static void
gcal_date_chooser_set_model (GcalTimelineSubscriber *subscriber,
GListModel *model)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (subscriber);
g_signal_group_set_target (self->model_signal_group, model);
queue_update_event_indicators (self);
}
static void
gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface)
{
iface->get_range = gcal_date_chooser_get_range;
iface->set_model = gcal_date_chooser_set_model;
}
static void
calendar_set_property (GObject *obj,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (obj);
switch (property_id)
{
case PROP_DATE:
gcal_date_chooser_set_date (GCAL_VIEW (self), g_value_get_boxed (value));
break;
case PROP_SHOW_HEADING:
gcal_date_chooser_set_show_heading (self, g_value_get_boolean (value));
break;
case PROP_SHOW_DAY_NAMES:
gcal_date_chooser_set_show_day_names (self, g_value_get_boolean (value));
break;
case PROP_SHOW_WEEK_NUMBERS:
gcal_date_chooser_set_show_week_numbers (self, g_value_get_boolean (value));
break;
case PROP_SHOW_SELECTED_WEEK:
gcal_date_chooser_set_show_selected_week (self, g_value_get_boolean (value));
break;
case PROP_SHOW_EVENTS:
gcal_date_chooser_set_show_events (self, g_value_get_boolean (value));
break;
case PROP_SPLIT_MONTH_YEAR:
gcal_date_chooser_set_split_month_year (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
calendar_get_property (GObject *obj,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (obj);
switch (property_id)
{
case PROP_DATE:
g_value_set_boxed (value, self->date);
break;
case PROP_SHOW_HEADING:
g_value_set_boolean (value, self->show_heading);
break;
case PROP_SHOW_DAY_NAMES:
g_value_set_boolean (value, self->show_day_names);
break;
case PROP_SHOW_WEEK_NUMBERS:
g_value_set_boolean (value, self->show_week_numbers);
break;
case PROP_SHOW_SELECTED_WEEK:
g_value_set_boolean (value, self->show_selected_week);
break;
case PROP_SHOW_EVENTS:
g_value_set_boolean (value, self->show_events);
break;
case PROP_SPLIT_MONTH_YEAR:
g_value_set_boolean (value, self->split_month_year);
break;
case PROP_TIME_DIRECTION:
g_value_set_enum (value, GTK_ORIENTATION_VERTICAL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
break;
}
}
static void
multi_choice_changed (GcalDateChooser *self,
gint year,
gint month,
gint day)
{
g_autoptr (GDateTime) date = NULL;
date = g_date_time_new_local (year, month, day, 0, 0, 0);
gcal_date_chooser_set_date (GCAL_VIEW (self), date);
}
static void
combined_multi_choice_changed (GcalDateChooser *self)
{
gint year, month, day, value;
value = gcal_multi_choice_get_value (GCAL_MULTI_CHOICE (self->combined_choice));
decode_combined_choice_value (value, &year, &month, &day);
multi_choice_changed (self, year, month, day);
}
static void
split_multi_choice_changed (GcalDateChooser *self)
{
gint year, month, day;
year = gcal_multi_choice_get_value (GCAL_MULTI_CHOICE (self->year_choice));
month = gcal_multi_choice_get_value (GCAL_MULTI_CHOICE (self->month_choice)) + 1;
day = g_date_time_get_day_of_month (self->date);
/* Make sure the day is valid at that month */
day = MIN (day, month_length[leap (year)][month]);
multi_choice_changed (self, year, month, day);
}
static gboolean
on_drop_target_drop_cb (GtkDropTarget *target,
const GValue *value,
double x,
double y,
GcalDateChooser *self)
{
g_autoptr (GDateTime) date = NULL;
g_autoptr (GDate) gdate = NULL;
const gchar *text;
gint year, month, day;
gdate = g_date_new ();
text = g_value_get_string (value);
if (text)
g_date_set_parse (gdate, text);
if (!g_date_valid (gdate))
return FALSE;
year = g_date_get_year (gdate);
month = g_date_get_month (gdate);
day = g_date_get_day (gdate);
if (!self->show_heading)
g_date_time_get_ymd (self->date, &year, &month, NULL);
date = g_date_time_new_local (year, month, day, 0, 0, 0);
gcal_date_chooser_set_date (GCAL_VIEW (self), date);
return TRUE;
}
static void
gcal_date_chooser_finalize (GObject *object)
{
GcalDateChooser *self = GCAL_DATE_CHOOSER (object);
g_clear_handle_id (&self->update_indicators_idle_id, g_source_remove);
g_clear_object (&self->model_signal_group);
g_clear_pointer (&self->date, g_date_time_unref);
G_OBJECT_CLASS (gcal_date_chooser_parent_class)->finalize (object);
}
static void
gcal_date_chooser_dispose (GObject *object)
{
G_OBJECT_CLASS (gcal_date_chooser_parent_class)->dispose (object);
}
static void
gcal_date_chooser_class_init (GcalDateChooserClass *class)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
GObjectClass *object_class = G_OBJECT_CLASS (class);
g_type_ensure (GCAL_TYPE_MULTI_CHOICE);
g_type_ensure (GCAL_TYPE_GIZMO);
object_class->dispose = gcal_date_chooser_dispose;
object_class->finalize = gcal_date_chooser_finalize;
object_class->set_property = calendar_set_property;
object_class->get_property = calendar_get_property;
properties[PROP_SHOW_HEADING] = g_param_spec_boolean ("show-heading",
"Show Heading",
"If TRUE, a heading is displayed",
TRUE,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
properties[PROP_SHOW_DAY_NAMES] = g_param_spec_boolean ("show-day-names",
"Show Day Names",
"If TRUE, day names are displayed",
TRUE,
G_PARAM_READWRITE);
properties[PROP_SHOW_WEEK_NUMBERS] = g_param_spec_boolean ("show-week-numbers",
"Show Week Numbers",
"If TRUE, week numbers are displayed",
TRUE,
G_PARAM_READWRITE);
/**
* GcalDateChooser:show-selected-week:
*
* Whether the selected week is highlighted or not.
*/
properties[PROP_SHOW_SELECTED_WEEK] = g_param_spec_boolean ("show-selected-week",
"Show Selected Week",
"If TRUE, week numbers are displayed",
TRUE,
G_PARAM_READWRITE);
properties[PROP_SHOW_EVENTS] = g_param_spec_boolean ("show-events",
"Show Events",
"If TRUE, events are displayed",
TRUE,
G_PARAM_READWRITE);
properties[PROP_SPLIT_MONTH_YEAR] = g_param_spec_boolean ("split-month-year",
"Split Month Year",
"If TRUE, the month and year selectors are split",
TRUE,
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
g_object_class_override_property (object_class, PROP_DATE, "active-date");
g_object_class_override_property (object_class, PROP_TIME_DIRECTION, "time-direction");
signals[DAY_SELECTED] = g_signal_new ("day-selected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
NULL,
G_TYPE_NONE, 0);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/common/gcal-date-chooser.ui");
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, combined_choice);
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, month_choice);
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, popover_month_choice);
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, year_choice);
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, grid);
gtk_widget_class_bind_template_child (widget_class, GcalDateChooser, gizmo);
gtk_widget_class_bind_template_callback (widget_class, combined_multi_choice_changed);
gtk_widget_class_bind_template_callback (widget_class, get_combined_choice_visible);
gtk_widget_class_bind_template_callback (widget_class, get_split_choice_visible);
gtk_widget_class_bind_template_callback (widget_class, split_multi_choice_changed);
gtk_widget_class_set_css_name (widget_class, "datechooser");
}
static gboolean
show_week_number_to_column_cb (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
g_value_set_int (to_value, g_value_get_boolean (from_value) ? -1 : 0);
return TRUE;
}
static gboolean
show_week_number_to_column_span_cb (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
g_value_set_int (to_value, g_value_get_boolean (from_value) ? N_WEEKDAYS + 1 : N_WEEKDAYS);
return TRUE;
}
static void
on_event_model_items_changed_cb (GListModel *model,
unsigned int position,
unsigned int removed,
unsigned int added,
GcalDateChooser *self)
{
queue_update_event_indicators (self);
}
static void
gcal_date_chooser_init (GcalDateChooser *self)
{
GcalContext *context = gcal_application_get_context (GCAL_DEFAULT_APPLICATION);
GtkDropTarget *drop_target;
GtkWidget *label;
gint row, col;
gint year, month, day;
GtkLayoutManager *layout_manager;
self->model_signal_group = g_signal_group_new (G_TYPE_LIST_MODEL);
g_signal_group_connect (self->model_signal_group,
"items-changed",
G_CALLBACK (on_event_model_items_changed_cb),
self);
self->show_heading = TRUE;
self->show_day_names = TRUE;
self->show_week_numbers = TRUE;
self->show_selected_week = TRUE;
self->show_events = TRUE;
self->split_month_year = TRUE;
self->date = g_date_time_new_now_local ();
g_date_time_get_ymd (self->date, &self->this_year, NULL, NULL);
self->week_start = get_first_weekday ();
gtk_widget_init_template (GTK_WIDGET (self));
gcal_gizmo_set_focus_func (self->gizmo, gcal_gizmo_focus, NULL);
for (col = 0; col < N_WEEKDAYS; col++)
{
self->cols[col] = gtk_label_new ("");
g_object_bind_property (self,
"show-day-names",
self->cols[col],
"visible",
G_BINDING_SYNC_CREATE);
gtk_widget_add_css_class (self->cols[col], "weekday");
gtk_grid_attach (GTK_GRID (self->grid), self->cols[col], col, -1, 1, 1);
}
layout_manager = gtk_widget_get_layout_manager (self->grid);
for (row = 0; row < ROWS; row++)
{
GtkLayoutChild *layout_child;
self->rows[row] = gtk_label_new ("");
g_object_bind_property (self,
"show-week-numbers",
self->rows[row],
"visible",
G_BINDING_SYNC_CREATE);
gtk_widget_add_css_class (self->rows[row], "weeknum");
gtk_grid_attach (GTK_GRID (self->grid), self->rows[row], -1, row, 1, 1);
self->week[row] = adw_bin_new ();
gtk_widget_set_visible (self->week[row], FALSE);
gtk_widget_add_css_class (self->week[row], "current-week");
gtk_grid_attach (GTK_GRID (self->grid), self->week[row], -1, row, 8, 1);
layout_child = gtk_layout_manager_get_layout_child (layout_manager, self->week[row]);
g_object_bind_property_full (self,
"show-week-numbers",
layout_child,
"column",
G_BINDING_SYNC_CREATE,
show_week_number_to_column_cb,
NULL,
NULL,
NULL);
g_object_bind_property_full (self,
"show-week-numbers",
layout_child,
"column-span",
G_BINDING_SYNC_CREATE,
show_week_number_to_column_span_cb,
NULL,
NULL,
NULL);
}
/* We are using a stack here to keep the week number column from shrinking
* when all the weeks are single-digit
*/
self->corner = gtk_stack_new ();
gtk_grid_attach (GTK_GRID (self->grid), self->corner, -1, -1, 1, 1);
label = gtk_label_new ("");
gtk_widget_add_css_class (label, "weekday");
gtk_stack_add_named (GTK_STACK (self->corner), label, "weekday");
label = gtk_label_new ("99");
gtk_widget_add_css_class (label, "weeknum");
gtk_stack_add_named (GTK_STACK (self->corner), label, "weeknum");
self->day_grid = g_object_new (GTK_TYPE_GRID,
"valign", GTK_ALIGN_FILL,
"halign", GTK_ALIGN_FILL,
"row-spacing", 2,
"column-spacing", 2,
"visible", TRUE,
NULL);
gtk_grid_attach (GTK_GRID (self->grid), self->day_grid, 0, 0, N_WEEKDAYS, ROWS);
for (row = 0; row < ROWS; row++)
{
for (col = 0; col < N_WEEKDAYS; col++)
{
self->days[row][col] = gcal_date_chooser_day_new ();
g_object_bind_property (self,
"show-events",
self->days[row][col],
"has-dot",
G_BINDING_SYNC_CREATE);
g_signal_connect_object (self->days[row][col],
"clicked",
G_CALLBACK (day_selected_cb),
self,
0);
gtk_grid_attach (GTK_GRID (self->day_grid), self->days[row][col], col, row, 1, 1);
}
}
calendar_init_month_display (self);
calendar_init_weekday_display (self);
calendar_compute_days (self);
g_date_time_get_ymd (self->date, &year, &month, &day);
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->year_choice), year);
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->month_choice), month - 1);
gcal_multi_choice_set_value (GCAL_MULTI_CHOICE (self->combined_choice),
encode_combined_choice_value (year, month, day));
calendar_update_selected_day_display (self);
gcal_multi_choice_set_value_callbacks (GCAL_MULTI_CHOICE (self->combined_choice),
compute_prev_combined_choice_value,
compute_next_combined_choice_value);
drop_target = gtk_drop_target_new (G_TYPE_STRING, GDK_ACTION_COPY);
gtk_drop_target_set_preload (drop_target, TRUE);
g_signal_connect (drop_target, "drop", G_CALLBACK (on_drop_target_drop_cb), NULL);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
g_signal_connect_object (gcal_context_get_clock (context),
"day-changed",
G_CALLBACK (on_clock_day_changed_cb),
self,
G_CONNECT_SWAPPED);
}
/*
* Public API
*/
/**
* gcal_date_chooser_new:
*
* Creates a new `DateChooser`.
*
* Returns: the newly created `DateChooser`
*/
GtkWidget*
gcal_date_chooser_new (void)
{
return g_object_new (GCAL_TYPE_DATE_CHOOSER, NULL);
}
void
gcal_date_chooser_set_show_heading (GcalDateChooser *self,
gboolean setting)
{
if (self->show_heading == setting)
return;
self->show_heading = setting;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_HEADING]);
}
gboolean
gcal_date_chooser_get_show_heading (GcalDateChooser *self)
{
return self->show_heading;
}
void
gcal_date_chooser_set_show_day_names (GcalDateChooser *self,
gboolean setting)
{
if (self->show_day_names == setting)
return;
self->show_day_names = setting;
gtk_widget_set_visible (self->corner, self->show_day_names && self->show_week_numbers);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_DAY_NAMES]);
}
gboolean
gcal_date_chooser_get_show_day_names (GcalDateChooser *self)
{
return self->show_day_names;
}
void
gcal_date_chooser_set_show_week_numbers (GcalDateChooser *self,
gboolean setting)
{
if (self->show_week_numbers == setting)
return;
self->show_week_numbers = setting;
gtk_widget_set_visible (self->corner, self->show_day_names && self->show_week_numbers);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_WEEK_NUMBERS]);
}
gboolean
gcal_date_chooser_get_show_week_numbers (GcalDateChooser *self)
{
return self->show_week_numbers;
}
/**
* gcal_date_chooser_set_show_selected_week:
* @self: a #GcalDateChooser
* @setting: whether the selected week is highlighted or not.
*
* Sets the selected week is highlighted or not.
*/
void
gcal_date_chooser_set_show_selected_week (GcalDateChooser *self,
gboolean setting)
{
if (self->show_selected_week == setting)
return;
self->show_selected_week = setting;
calendar_update_selected_day_display (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_SELECTED_WEEK]);
}
/**
* gcal_date_chooser_get_show_selected_week:
* @self: a #GcalDateChooser
*
* Returns: whether the selected week is highlighted or not.
*/
gboolean
gcal_date_chooser_get_show_selected_week (GcalDateChooser *self)
{
return self->show_selected_week;
}
void
gcal_date_chooser_set_show_events (GcalDateChooser *self,
gboolean setting)
{
if (self->show_events == setting)
return;
self->show_events = setting;
calendar_update_selected_day_display (self);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SHOW_EVENTS]);
}
gboolean
gcal_date_chooser_get_show_events (GcalDateChooser *self)
{
return self->show_events;
}
void
gcal_date_chooser_set_split_month_year (GcalDateChooser *self,
gboolean setting)
{
if (self->split_month_year == setting)
return;
self->split_month_year = setting;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SPLIT_MONTH_YEAR]);
}
gboolean
gcal_date_chooser_get_split_month_year (GcalDateChooser *self)
{
return self->split_month_year;
}