/* 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 . */ #include "config.h" #define G_LOG_DOMAIN "GcalDateChooser" #include "gcal-utils.h" #include "gcal-date-chooser.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 #include #include #include #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; GtkWidget *grid; GtkWidget *day_grid; GtkWidget *corner; GtkWidget *cols[N_WEEKDAYS]; GtkWidget *rows[ROWS]; GtkWidget *days[ROWS][N_WEEKDAYS]; GtkWidget *week[ROWS]; GDateTime *date; 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; GcalRangeTree *events; gulong 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) { gint row, col; for (row = 0; row < ROWS; row++) { for (col = 0; col < N_WEEKDAYS; col++) { GDateTime *date; g_autoptr (GcalRange) range = NULL; date = gcal_date_chooser_day_get_date (GCAL_DATE_CHOOSER_DAY (self->days[row][col])); range = gcal_range_new_take (g_date_time_ref (date), g_date_time_add_days (date, 1), GCAL_RANGE_DEFAULT); gcal_date_chooser_day_set_dot_visible (GCAL_DATE_CHOOSER_DAY (self->days[row][col]), gcal_range_tree_count_entries_at_range (self->events, range) > 0); } } } 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); 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; } /* * 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_add_event (GcalTimelineSubscriber *subscriber, GcalEvent *event) { GcalDateChooser *self; self = GCAL_DATE_CHOOSER (subscriber); g_debug ("Caching event '%s' in %s", gcal_event_get_uid (event), G_OBJECT_TYPE_NAME (self)); gcal_range_tree_add_range (self->events, gcal_event_get_range (event), g_object_ref (event)); queue_update_event_indicators (self); } static void gcal_date_chooser_remove_event (GcalTimelineSubscriber *subscriber, GcalEvent *event) { GcalDateChooser *self; self = GCAL_DATE_CHOOSER (subscriber); g_debug ("Removing event '%s' from %s's cache", gcal_event_get_uid (event), G_OBJECT_TYPE_NAME (self)); gcal_range_tree_remove_range (self->events, gcal_event_get_range (event), event); queue_update_event_indicators (self); } static void gcal_date_chooser_update_event (GcalTimelineSubscriber *subscriber, GcalEvent *old_event, GcalEvent *event) { gcal_date_chooser_remove_event (subscriber, old_event); gcal_date_chooser_add_event (subscriber, event); } static void gcal_timeline_subscriber_interface_init (GcalTimelineSubscriberInterface *iface) { iface->get_range = gcal_date_chooser_get_range; iface->add_event = gcal_date_chooser_add_event; iface->remove_event = gcal_date_chooser_remove_event; iface->update_event = gcal_date_chooser_update_event; } 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_pointer (&self->date, g_date_time_unref); g_clear_pointer (&self->events, gcal_range_tree_unref); G_OBJECT_CLASS (gcal_date_chooser_parent_class)->finalize (object); } static gboolean gcal_date_chooser_child_focus (GtkWidget *widget, GtkDirectionType direction) { GcalDateChooser *self = GCAL_DATE_CHOOSER (widget); GtkRoot *root; GtkWidget *focus_widget, *new_focus; gboolean is_tab, is_rtl, left_or_right; is_tab = direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD; if (gtk_widget_get_focus_child (self->grid)) { if (is_tab) { root = gtk_widget_get_root (self->grid); while (gtk_widget_get_focus_child (self->grid)) { GtkWidget *previous_widget = gtk_root_get_focus (root); GTK_WIDGET_CLASS (gcal_date_chooser_parent_class)->focus (widget, direction); if (previous_widget == gtk_root_get_focus (root)) return FALSE; } return TRUE; } if (gtk_widget_child_focus (self->grid, direction)) return TRUE; left_or_right = direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT; if (left_or_right) { is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; root = gtk_widget_get_root (self->grid); 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); } gtk_widget_error_bell (self->grid); return TRUE; } return GTK_WIDGET_CLASS (gcal_date_chooser_parent_class)->focus (widget, direction); } 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); 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; widget_class->focus = gcal_date_chooser_child_focus; 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_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 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->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 (); self->events = gcal_range_tree_new_with_free_func (g_object_unref); gtk_widget_init_template (GTK_WIDGET (self)); 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; }