From 2f86a7a77b9cfe96cdec44f69421c486d1cf9a11 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Sat, 5 Oct 2024 15:18:20 -0600 Subject: [PATCH 01/63] Move explanatory comment We'll remove the date-start property, so put its description in the corresponding setter function. Part-of: --- src/gui/gcal-event-widget.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/gui/gcal-event-widget.c b/src/gui/gcal-event-widget.c index fdc71caec..4e49d444b 100644 --- a/src/gui/gcal-event-widget.c +++ b/src/gui/gcal-event-widget.c @@ -807,10 +807,6 @@ gcal_event_widget_class_init (GcalEventWidgetClass *klass) /** * GcalEventWidget::date-start: * - * The start date this widget represents. Notice that this may - * differ from the event's start date. For example, if the event - * spans more than one month and we're in Month View, the start - * date marks the first day this event widget is visible. */ g_object_class_install_property (object_class, PROP_DATE_START, @@ -1014,6 +1010,11 @@ gcal_event_widget_get_date_start (GcalEventWidget *self) * Sets the visible start date of this widget. This * may differ from the event's start date, but cannot * be before it. + * + * The start date for this widget may differ from the event's start + * date. For example, if the event spans more than one month and we're + * in Month View, the start date marks the first day this event widget + * is visible. */ void gcal_event_widget_set_date_start (GcalEventWidget *self, -- GitLab From 6e10e746c35897aa917daf0a2c5714b75c5deeef Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Sat, 5 Oct 2024 15:20:09 -0600 Subject: [PATCH 02/63] GCalEventWidget: remove the date-start property; it is never used Setting and getting the value always happens through gcal_event_widget_set_date_start() and gcal_event_widget_get_date_start(). Part-of: --- src/gui/gcal-event-widget.c | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/gui/gcal-event-widget.c b/src/gui/gcal-event-widget.c index 4e49d444b..dd15c0c36 100644 --- a/src/gui/gcal-event-widget.c +++ b/src/gui/gcal-event-widget.c @@ -81,7 +81,6 @@ enum PROP_0, PROP_CONTEXT, PROP_DATE_END, - PROP_DATE_START, PROP_EVENT, PROP_TIMESTAMP_POLICY, PROP_ORIENTATION, @@ -670,10 +669,6 @@ gcal_event_widget_set_property (GObject *object, gcal_event_widget_set_date_end (self, g_value_get_boxed (value)); break; - case PROP_DATE_START: - gcal_event_widget_set_date_start (self, g_value_get_boxed (value)); - break; - case PROP_EVENT: gcal_event_widget_set_event_internal (self, g_value_get_object (value)); break; @@ -713,10 +708,6 @@ gcal_event_widget_get_property (GObject *object, g_value_set_boxed (value, self->dt_end); break; - case PROP_DATE_START: - g_value_set_boxed (value, self->dt_start); - break; - case PROP_EVENT: g_value_set_object (value, self->event); break; @@ -804,18 +795,6 @@ gcal_event_widget_class_init (GcalEventWidgetClass *klass) G_TYPE_DATE_TIME, G_PARAM_READWRITE)); - /** - * GcalEventWidget::date-start: - * - */ - g_object_class_install_property (object_class, - PROP_DATE_START, - g_param_spec_boxed ("date-start", - "Start date", - "The start date of the widget", - G_TYPE_DATE_TIME, - G_PARAM_READWRITE)); - /** * GcalEventWidget::event: * @@ -1034,8 +1013,6 @@ gcal_event_widget_set_date_start (GcalEventWidget *self, self->dt_start = g_date_time_ref (date_start); gcal_event_widget_update_style (self); - - g_object_notify (G_OBJECT (self), "date-start"); } } -- GitLab From 7a814cc1f35042352a9bc8af2cb102166eb792ee Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Sat, 5 Oct 2024 15:29:17 -0600 Subject: [PATCH 03/63] Move explanatory comment Analogous to date-start Part-of: --- src/gui/gcal-event-widget.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/gui/gcal-event-widget.c b/src/gui/gcal-event-widget.c index dd15c0c36..261fa8415 100644 --- a/src/gui/gcal-event-widget.c +++ b/src/gui/gcal-event-widget.c @@ -782,10 +782,6 @@ gcal_event_widget_class_init (GcalEventWidgetClass *klass) /** * GcalEventWidget::date-end: * - * The end date this widget represents. Notice that this may - * differ from the event's end date. For example, if the event - * spans more than one month and we're in Month View, the end - * date marks the last day this event widget is visible. */ g_object_class_install_property (object_class, PROP_DATE_END, @@ -940,6 +936,11 @@ gcal_event_widget_get_date_end (GcalEventWidget *self) * Sets the visible end date of this widget. This * may differ from the event's end date, but cannot * be after it. + * + * The end date for this widget may differ from the event's end + * date. For example, if the event spans more than one month and we're + * in Month View, the end date marks the last day this event widget is + * visible. */ void gcal_event_widget_set_date_end (GcalEventWidget *self, -- GitLab From 72762ee11d0f8384850325c1978ab13b3fe816dc Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Sat, 5 Oct 2024 15:30:19 -0600 Subject: [PATCH 04/63] GcalEventWidget: remove the date-end property; it is unused Part-of: --- src/gui/gcal-event-widget.c | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/src/gui/gcal-event-widget.c b/src/gui/gcal-event-widget.c index 261fa8415..487c49087 100644 --- a/src/gui/gcal-event-widget.c +++ b/src/gui/gcal-event-widget.c @@ -80,7 +80,6 @@ enum { PROP_0, PROP_CONTEXT, - PROP_DATE_END, PROP_EVENT, PROP_TIMESTAMP_POLICY, PROP_ORIENTATION, @@ -665,10 +664,6 @@ gcal_event_widget_set_property (GObject *object, G_CONNECT_SWAPPED); break; - case PROP_DATE_END: - gcal_event_widget_set_date_end (self, g_value_get_boxed (value)); - break; - case PROP_EVENT: gcal_event_widget_set_event_internal (self, g_value_get_object (value)); break; @@ -704,10 +699,6 @@ gcal_event_widget_get_property (GObject *object, g_value_set_object (value, self->context); break; - case PROP_DATE_END: - g_value_set_boxed (value, self->dt_end); - break; - case PROP_EVENT: g_value_set_object (value, self->event); break; @@ -779,17 +770,6 @@ gcal_event_widget_class_init (GcalEventWidgetClass *klass) "Context", GCAL_TYPE_CONTEXT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); - /** - * GcalEventWidget::date-end: - * - */ - g_object_class_install_property (object_class, - PROP_DATE_END, - g_param_spec_boxed ("date-end", - "End date", - "The end date of the widget", - G_TYPE_DATE_TIME, - G_PARAM_READWRITE)); /** * GcalEventWidget::event: @@ -960,8 +940,6 @@ gcal_event_widget_set_date_end (GcalEventWidget *self, self->dt_end = g_date_time_ref (date_end); gcal_event_widget_update_style (self); - - g_object_notify (G_OBJECT (self), "date-end"); } } -- GitLab From 42b00ec0526c2adc8a9275d2fcc9e1cd640a185a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Sat, 5 Oct 2024 15:30:41 -0600 Subject: [PATCH 05/63] Fix cut&paste error Part-of: --- src/gui/gcal-event-widget.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gcal-event-widget.c b/src/gui/gcal-event-widget.c index 487c49087..5d5bf997c 100644 --- a/src/gui/gcal-event-widget.c +++ b/src/gui/gcal-event-widget.c @@ -963,7 +963,7 @@ gcal_event_widget_get_date_start (GcalEventWidget *self) /** * gcal_event_widget_set_date_start: * @self: a #GcalEventWidget - * @date_end: the start date of this widget + * @date_start: the start date of this widget * * Sets the visible start date of this widget. This * may differ from the event's start date, but cannot -- GitLab From f75785a34c9c372a96e783957297bef96181bf46 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 7 Oct 2024 20:11:34 -0600 Subject: [PATCH 06/63] GcalScheduleValues: new struct to hold values from a GcalEvent for the schedule widges We'll move GcalScheduleSection to operate without a backing GcalEvent, so we can add tests. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 44 ++++++++++++++++++++ src/gui/event-editor/gcal-schedule-section.h | 18 ++++++++ 2 files changed, 62 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index d5f33498a..fa3ce44ce 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -771,3 +771,47 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) GCAL_RETURN (gcal_date_time_compare_date (start_date, gcal_event_get_date_start (self->event)) < 0 || gcal_date_time_compare_date (end_date, gcal_event_get_date_end (self->event)) > 0); } + +/** + * Extracts the values from @event that are needed to populate #GcalScheduleSection. + * + * Returns: a #GcalscheduleValues ready for use. Free it with + * gcal_schedule_values_free(). + */ +GcalScheduleValues * +gcal_schedule_values_from_event (GcalEvent *event) +{ + GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); + + if (event) + { + GcalRecurrence *recur = gcal_event_get_recurrence (event); + + values->all_day = gcal_event_get_all_day (event); + values->date_start = g_date_time_ref (gcal_event_get_date_start (event)); + values->date_end = g_date_time_ref (gcal_event_get_date_end (event)); + + if (recur) + { + values->recur = gcal_recurrence_ref (recur); + } + } + + return values; +} + +/** + * Frees the contents of @values. Does not free the @values pointer itself; + * this structure is meant to be allocated on the stack. + */ +void +gcal_schedule_values_free (GcalScheduleValues *values) +{ + values->all_day = FALSE; + + g_clear_pointer (&values->date_start, g_date_time_unref); + g_clear_pointer (&values->date_end, g_date_time_unref); + g_clear_pointer (&values->recur, gcal_recurrence_unref); + + g_free (values); +} diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index e817a33b3..df0a2ffe9 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -28,8 +28,26 @@ G_BEGIN_DECLS #define GCAL_TYPE_SCHEDULE_SECTION (gcal_schedule_section_get_type()) G_DECLARE_FINAL_TYPE (GcalScheduleSection, gcal_schedule_section, GCAL, SCHEDULE_SECTION, GtkBox) +/* Values from a GcalEvent that this widget can manipulate. + * + * We keep an immutable copy of the original event's values, and later generate + * a new GcalScheduleValues with the updated data from the widgetry. + */ +typedef struct +{ + gboolean all_day; + GDateTime *date_start; + GDateTime *date_end; + GcalRecurrence *recur; +} GcalScheduleValues; + gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self); gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self); +GcalScheduleValues *gcal_schedule_values_from_event (GcalEvent *event); +void gcal_schedule_values_free (GcalScheduleValues *values); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleValues, gcal_schedule_values_free); + G_END_DECLS -- GitLab From d44e6847c7026390a7d1288c30af0750ae1f5192 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 7 Oct 2024 20:24:44 -0600 Subject: [PATCH 07/63] GcalScheduleSection: store the values when an event is set We don't use them yet; just hooking things up for now. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index fa3ce44ce..a48d6bca5 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -37,6 +37,8 @@ struct _GcalScheduleSection { GtkBox parent; + GcalScheduleValues *values; + AdwToggleGroup *schedule_type_toggle_group; AdwPreferencesGroup *start_date_group; GcalDateChooserRow *start_date_row; @@ -354,6 +356,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, g_set_object (&self->event, event); self->flags = flags; + self->values = gcal_schedule_values_from_event (event); + if (!event) GCAL_RETURN (); @@ -624,6 +628,7 @@ gcal_schedule_section_finalize (GObject *object) { GcalScheduleSection *self = (GcalScheduleSection *)object; + g_clear_pointer (&self->values, gcal_schedule_values_free); g_clear_object (&self->context); g_clear_object (&self->event); -- GitLab From 68126bcfacedfc552476445471de497cc48a46b9 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 24 Feb 2025 20:49:51 -0600 Subject: [PATCH 08/63] gcal_schedule_section_set_event(): set the widgets from the extracted values, not from the event Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 26 +++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index a48d6bca5..98ce0b37d 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -346,8 +346,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GDateTime* date_time_end = NULL; GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; - GcalRecurrence *recur; gboolean all_day, new_event; + GcalScheduleValues *values; GCAL_ENTRY; @@ -357,11 +357,12 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, self->flags = flags; self->values = gcal_schedule_values_from_event (event); + values = self->values; /* alias to avoid "self->values" everywhere */ if (!event) GCAL_RETURN (); - all_day = gcal_event_get_all_day (event); + all_day = values->all_day; new_event = flags & GCAL_EVENT_EDITOR_FLAG_NEW_EVENT; /* schedule type */ @@ -374,8 +375,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), !all_day); /* retrieve start and end date-times */ - date_time_start = gcal_event_get_date_start (event); - date_time_end = gcal_event_get_date_end (event); + date_time_start = values->date_start; + date_time_end = values->date_end; /* * This is subtracting what has been added in action_button_clicked (). * See bug 769300. @@ -427,9 +428,16 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, unblock_date_signals (self); /* Recurrences */ - recur = gcal_event_get_recurrence (event); - frequency = recur ? recur->frequency : GCAL_RECURRENCE_NO_REPEAT; - limit_type = recur ? recur->limit_type : GCAL_RECURRENCE_FOREVER; + if (values->recur) + { + frequency = values->recur->frequency; + limit_type = values->recur->limit_type; + } + else + { + frequency = GCAL_RECURRENCE_NO_REPEAT; + limit_type = GCAL_RECURRENCE_FOREVER; + } adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_combo), frequency); adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), limit_type); @@ -447,11 +455,11 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, switch (limit_type) { case GCAL_RECURRENCE_COUNT: - gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), recur->limit.count); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), values->recur->limit.count); break; case GCAL_RECURRENCE_UNTIL: - gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), recur->limit.until); + gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), values->recur->limit.until); break; case GCAL_RECURRENCE_FOREVER: -- GitLab From f51186c298560caa76900e448678f02d6622ad77 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 24 Feb 2025 20:59:00 -0600 Subject: [PATCH 09/63] gcal_schedule_section_apply(): use the stored values as a reference Also, update the values from the updated event at the end. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 98ce0b37d..1559188d1 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -476,7 +476,6 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) GDateTime *start_date, *end_date; GcalRecurrenceFrequency freq; GcalScheduleSection *self; - GcalRecurrence *old_recur; gboolean all_day; GCAL_ENTRY; @@ -524,7 +523,7 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) gcal_event_set_date_end (self->event, end_date); /* Check Repeat popover and set recurrence-rules accordingly */ - old_recur = gcal_event_get_recurrence (self->event); + freq = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); if (freq != GCAL_RECURRENCE_NO_REPEAT) @@ -541,7 +540,7 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) recur->limit.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); /* Only apply the new recurrence if it's different from the old one */ - if (!gcal_recurrence_is_equal (old_recur, recur)) + if (!gcal_recurrence_is_equal (self->values->recur, recur)) { /* Remove the previous recurrence... */ remove_recurrence_properties (self->event); @@ -556,6 +555,9 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) remove_recurrence_properties (self->event); } + gcal_schedule_values_free (self->values); + self->values = gcal_schedule_values_from_event (self->event); + GCAL_EXIT; } -- GitLab From 70173639e45dcc5578fc72d9216457efd50cd769 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 24 Feb 2025 21:08:51 -0600 Subject: [PATCH 10/63] gcal-schedule-section.c: Use the stored values instead of pulling them from the event every time Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 1559188d1..c0958af55 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -574,7 +574,7 @@ gcal_schedule_section_changed (GcalEventEditorSection *section) self = GCAL_SCHEDULE_SECTION (section); all_day = all_day_selected (self); - was_all_day = gcal_event_get_all_day (self->event); + was_all_day = self->values->all_day; /* All day */ if (all_day != was_all_day) @@ -590,8 +590,8 @@ gcal_schedule_section_changed (GcalEventEditorSection *section) start_date = gcal_date_chooser_row_get_date (self->start_date_row); end_date = gcal_date_chooser_row_get_date (self->end_date_row); } - prev_start_date = gcal_event_get_date_start (self->event); - prev_end_date = gcal_event_get_date_end (self->event); + prev_start_date = self->values->date_start; + prev_end_date = self->values->date_end; start_tz = g_date_time_get_timezone (start_date); end_tz = g_date_time_get_timezone (end_date); @@ -611,7 +611,7 @@ gcal_schedule_section_changed (GcalEventEditorSection *section) end_date = fake_end_date; } - if (!g_date_time_equal (end_date, gcal_event_get_date_end (self->event))) + if (!g_date_time_equal (end_date, self->values->date_end)) GCAL_RETURN (TRUE); if (g_strcmp0 (g_time_zone_get_identifier (end_tz), g_time_zone_get_identifier (prev_end_tz)) != 0) GCAL_RETURN (TRUE); @@ -746,7 +746,7 @@ gcal_schedule_section_recurrence_changed (GcalScheduleSection *self) g_return_val_if_fail (GCAL_IS_SCHEDULE_SECTION (self), FALSE); freq = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); - if (freq == GCAL_RECURRENCE_NO_REPEAT && !gcal_event_get_recurrence (self->event)) + if (freq == GCAL_RECURRENCE_NO_REPEAT && !self->values->recur) GCAL_RETURN (FALSE); recurrence = gcal_recurrence_new (); @@ -757,7 +757,7 @@ gcal_schedule_section_recurrence_changed (GcalScheduleSection *self) else if (recurrence->limit_type == GCAL_RECURRENCE_COUNT) recurrence->limit.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); - GCAL_RETURN (!gcal_recurrence_is_equal (recurrence, gcal_event_get_recurrence (self->event))); + GCAL_RETURN (!gcal_recurrence_is_equal (recurrence, self->values->recur)); } gboolean @@ -783,8 +783,8 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) end_date = gcal_date_chooser_row_get_date (self->end_date_row); } - GCAL_RETURN (gcal_date_time_compare_date (start_date, gcal_event_get_date_start (self->event)) < 0 || - gcal_date_time_compare_date (end_date, gcal_event_get_date_end (self->event)) > 0); + GCAL_RETURN (gcal_date_time_compare_date (start_date, self->values->date_start) < 0 || + gcal_date_time_compare_date (end_date, self->values->date_end) > 0); } /** -- GitLab From 939b60d6b4c701741343f02169b617e1bb0d9016 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 24 Feb 2025 21:32:56 -0600 Subject: [PATCH 11/63] gcal_schedule_section_apply(): extract an _apply_to_event() helper function With this, there's no direct mutation of the self->event anywhere; the _apply() function just uses self->event as a target for mutation because that's what the surrounding architecture expects. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index c0958af55..2625c987b 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -471,17 +471,15 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, } static void -gcal_schedule_section_apply (GcalEventEditorSection *section) +gcal_schedule_section_apply_to_event (GcalScheduleSection *self, + GcalEvent *event) { GDateTime *start_date, *end_date; GcalRecurrenceFrequency freq; - GcalScheduleSection *self; gboolean all_day; GCAL_ENTRY; - self = GCAL_SCHEDULE_SECTION (section); - all_day = all_day_selected (self); if (!all_day) @@ -505,7 +503,7 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) } #endif - gcal_event_set_all_day (self->event, all_day); + gcal_event_set_all_day (event, all_day); /* * The end date for multi-day events is exclusive, so we bump it by a day. @@ -519,8 +517,8 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) end_date = fake_end_date; } - gcal_event_set_date_start (self->event, start_date); - gcal_event_set_date_end (self->event, end_date); + gcal_event_set_date_start (event, start_date); + gcal_event_set_date_end (event, end_date); /* Check Repeat popover and set recurrence-rules accordingly */ @@ -543,18 +541,30 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) if (!gcal_recurrence_is_equal (self->values->recur, recur)) { /* Remove the previous recurrence... */ - remove_recurrence_properties (self->event); + remove_recurrence_properties (event); /* ... and set the new one */ - gcal_event_set_recurrence (self->event, recur); + gcal_event_set_recurrence (event, recur); } } else { /* When NO_REPEAT is set, make sure to remove the old recurrent */ - remove_recurrence_properties (self->event); + remove_recurrence_properties (event); } + GCAL_EXIT; +} + +static void +gcal_schedule_section_apply (GcalEventEditorSection *section) +{ + GcalScheduleSection *self = GCAL_SCHEDULE_SECTION (section); + + GCAL_ENTRY; + + gcal_schedule_section_apply_to_event (self, self->event); + gcal_schedule_values_free (self->values); self->values = gcal_schedule_values_from_event (self->event); -- GitLab From a4d891aaddd08f97856794b44f8bde52088d64fe Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 09:46:33 -0600 Subject: [PATCH 12/63] New function to copy a GcalScheduleValues These structs are immutable. Any changes will be made on a copy. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 2625c987b..7ffab255d 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -85,6 +85,18 @@ static void on_end_date_time_changed_cb (GtkWidget GParamSpec *pspec, GcalScheduleSection *self); +static GcalScheduleValues * +gcal_schedule_values_copy (const GcalScheduleValues *values) +{ + GcalScheduleValues *copy = g_new0 (GcalScheduleValues, 1); + + copy->all_day = values->all_day; + copy->date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; + copy->date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; + copy->recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; + + return copy; +} /* * Auxiliary methods -- GitLab From 2c476f6d0270a328ad46582e3cbd1ddbd70132c1 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 09:47:27 -0600 Subject: [PATCH 13/63] New function to set the all_day flag on GcalScheduleValues You may think that this is too much indirection. But wait! This is the start of enabling tests. Hang in there. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 7ffab255d..572bdaa33 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -98,6 +98,15 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) return copy; } +static GcalScheduleValues * +gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all_day) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + copy->all_day = all_day; + return copy; +} + /* * Auxiliary methods */ -- GitLab From 640488883bd2084715f8af5535c913c2c0b362af Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 20:47:15 -0600 Subject: [PATCH 14/63] Introduce a WidgetState structure and compute it from GcalScheduleValues You may think, yet more indirection! But behold: 1. We take events from the widgets ("schedule type (all-day) changed") and turn them into actions (gcal_schedule_values_set_all_day()). 2. Those actions (e.g. that function) decide how to change the GcalScheduleValues. 3. We compute the desired state of the wigets, based on the new values. 4. We set the state of the widgets based on that. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 51 ++++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 572bdaa33..422b2356e 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -57,6 +57,22 @@ struct _GcalScheduleSection GcalEventEditorFlags flags; }; +/* Desired state of the widgets, computed from GcalScheduleValues. + * + * There is some subtle logic to decide how to update the widgets based on how + * each of them causes the GcalScheduleValues to be modified. We encapsulate + * the desired state in this struct, so we can have tests for it. + */ +typedef struct +{ + gboolean date_widgets_visible; + gboolean date_time_widgets_visible; +} WidgetState; + +static void widget_state_free (WidgetState *state); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (WidgetState, widget_state_free); + static void gcal_event_editor_section_iface_init (GcalEventEditorSectionInterface *iface); G_DEFINE_TYPE_WITH_CODE (GcalScheduleSection, gcal_schedule_section, GTK_TYPE_BOX, @@ -107,6 +123,23 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all return copy; } +static WidgetState * +widget_state_from_values (const GcalScheduleValues *values) +{ + WidgetState *state = g_new0 (WidgetState, 1); + + state->date_widgets_visible = values->all_day; + state->date_time_widgets_visible = !values->all_day; + + return state; +} + +static void +widget_state_free (WidgetState *state) +{ + g_free (state); +} + /* * Auxiliary methods */ @@ -151,6 +184,15 @@ remove_recurrence_properties (GcalEvent *event) e_cal_component_commit_sequence (comp); } +static void +update_widgets (GcalScheduleSection *self, + WidgetState *state) +{ + gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), state->date_widgets_visible); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), state->date_widgets_visible); + gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), state->date_time_widgets_visible); + gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), state->date_time_widgets_visible); +} /* * Callbacks @@ -162,14 +204,13 @@ on_schedule_type_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { gboolean all_day = all_day_selected (self); - - gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), all_day); - gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), !all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), !all_day); + GcalScheduleValues *updated = gcal_schedule_values_set_all_day (self->values, all_day); + g_autoptr (WidgetState) state = widget_state_from_values (updated); block_date_signals (self); + update_widgets (self, state); + if (all_day) { g_autoptr (GDateTime) start_local = NULL; -- GitLab From 253b1d4f17f40048de64a8771f5ff0d2e494012b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 20:53:36 -0600 Subject: [PATCH 15/63] Start transitioning gcal_schedule_section_set_event() to use update_widgets() For now this is redundant, as update_widgets() only sets the visibility of the time selectors, while the call to adw_toggle_group_set_active_name() effectively does the same. But that redundancy will go away in the next step. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 422b2356e..e0eddfd66 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -188,6 +188,7 @@ static void update_widgets (GcalScheduleSection *self, WidgetState *state) { + gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), state->date_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), state->date_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), state->date_time_widgets_visible); @@ -410,6 +411,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalRecurrenceFrequency frequency; gboolean all_day, new_event; GcalScheduleValues *values; + g_autoptr (WidgetState) state = NULL; GCAL_ENTRY; @@ -431,10 +433,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, adw_toggle_group_set_active_name (self->schedule_type_toggle_group, all_day ? "all-day" : "time-slot"); - gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), all_day); - gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), !all_day); - gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), !all_day); + state = widget_state_from_values (values); + update_widgets (self, state); /* retrieve start and end date-times */ date_time_start = values->date_start; -- GitLab From 11dcab5ddf769a046bacdaba6af58001030e7c4f Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 21:06:18 -0600 Subject: [PATCH 16/63] Set the state of the schedule_type_toggle_group in update_widgets() And now the redundancy in gcal_schedule_section_set_event() is gone. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index e0eddfd66..5ed22c52a 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -65,6 +65,7 @@ struct _GcalScheduleSection */ typedef struct { + gboolean schedule_type_all_day; gboolean date_widgets_visible; gboolean date_time_widgets_visible; } WidgetState; @@ -101,6 +102,10 @@ static void on_end_date_time_changed_cb (GtkWidget GParamSpec *pspec, GcalScheduleSection *self); +static void on_schedule_type_changed_cb (GtkWidget *widget, + GParamSpec *pspec, + GcalScheduleSection *self); + static GcalScheduleValues * gcal_schedule_values_copy (const GcalScheduleValues *values) { @@ -128,6 +133,7 @@ widget_state_from_values (const GcalScheduleValues *values) { WidgetState *state = g_new0 (WidgetState, 1); + state->schedule_type_all_day = values->all_day; state->date_widgets_visible = values->all_day; state->date_time_widgets_visible = !values->all_day; @@ -188,11 +194,17 @@ static void update_widgets (GcalScheduleSection *self, WidgetState *state) { + g_signal_handlers_block_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); + + adw_toggle_group_set_active_name (self->schedule_type_toggle_group, + state->schedule_type_all_day ? "all-day" : "time-slot"); gtk_widget_set_visible (GTK_WIDGET (self->start_date_group), state->date_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->end_date_group), state->date_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), state->date_time_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), state->date_time_widgets_visible); + + g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } /* @@ -429,10 +441,6 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, all_day = values->all_day; new_event = flags & GCAL_EVENT_EDITOR_FLAG_NEW_EVENT; - /* schedule type */ - adw_toggle_group_set_active_name (self->schedule_type_toggle_group, - all_day ? "all-day" : "time-slot"); - state = widget_state_from_values (values); update_widgets (self, state); -- GitLab From 938419c1d4338b59a3f902e6a9f4dd0c1be70d21 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 25 Feb 2025 22:11:01 -0600 Subject: [PATCH 17/63] Handle the time_format as part of GcalScheduleValues and WidgetState This will let us write tests for changing the time format without a context object. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 36 ++++++++++++++++---- src/gui/event-editor/gcal-schedule-section.h | 4 ++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 5ed22c52a..fca37d5aa 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -68,6 +68,7 @@ typedef struct gboolean schedule_type_all_day; gboolean date_widgets_visible; gboolean date_time_widgets_visible; + GcalTimeFormat time_format; } WidgetState; static void widget_state_free (WidgetState *state); @@ -115,6 +116,7 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) copy->date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; copy->date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; copy->recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; + copy->time_format = values->time_format; return copy; } @@ -128,6 +130,15 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all return copy; } +static GcalScheduleValues * +gcal_schedule_values_set_time_format (const GcalScheduleValues *values, GcalTimeFormat time_format) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + copy->time_format = time_format; + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleValues *values) { @@ -136,6 +147,7 @@ widget_state_from_values (const GcalScheduleValues *values) state->schedule_type_all_day = values->all_day; state->date_widgets_visible = values->all_day; state->date_time_widgets_visible = !values->all_day; + state->time_format = values->time_format; return state; } @@ -204,6 +216,9 @@ update_widgets (GcalScheduleSection *self, gtk_widget_set_visible (GTK_WIDGET (self->start_date_time_chooser), state->date_time_widgets_visible); gtk_widget_set_visible (GTK_WIDGET (self->end_date_time_chooser), state->date_time_widgets_visible); + gcal_date_time_chooser_set_time_format (self->start_date_time_chooser, state->time_format); + gcal_date_time_chooser_set_time_format (self->end_date_time_chooser, state->time_format); + g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } @@ -387,8 +402,11 @@ on_time_format_changed_cb (GcalScheduleSection *self) { GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - gcal_date_time_chooser_set_time_format (self->start_date_time_chooser, time_format); - gcal_date_time_chooser_set_time_format (self->end_date_time_chooser, time_format); + GcalScheduleValues *updated = gcal_schedule_values_set_time_format (self->values, time_format); + + WidgetState *state = widget_state_from_values (updated); + update_widgets (self, state); + widget_state_free (state); } @@ -422,6 +440,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; gboolean all_day, new_event; + GcalTimeFormat time_format; GcalScheduleValues *values; g_autoptr (WidgetState) state = NULL; @@ -432,7 +451,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, g_set_object (&self->event, event); self->flags = flags; - self->values = gcal_schedule_values_from_event (event); + time_format = gcal_context_get_time_format (self->context); + self->values = gcal_schedule_values_from_event (event, time_format); values = self->values; /* alias to avoid "self->values" everywhere */ if (!event) @@ -636,7 +656,9 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) gcal_schedule_section_apply_to_event (self, self->event); gcal_schedule_values_free (self->values); - self->values = gcal_schedule_values_from_event (self->event); + + GcalTimeFormat time_format = gcal_context_get_time_format (self->context); + self->values = gcal_schedule_values_from_event (self->event, time_format); GCAL_EXIT; } @@ -762,7 +784,6 @@ gcal_schedule_section_set_property (GObject *object, G_CALLBACK (on_time_format_changed_cb), self, G_CONNECT_SWAPPED); - on_time_format_changed_cb (self); break; default: @@ -874,7 +895,8 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) * gcal_schedule_values_free(). */ GcalScheduleValues * -gcal_schedule_values_from_event (GcalEvent *event) +gcal_schedule_values_from_event (GcalEvent *event, + GcalTimeFormat time_format) { GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); @@ -892,6 +914,8 @@ gcal_schedule_values_from_event (GcalEvent *event) } } + values->time_format = time_format; + return values; } diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index df0a2ffe9..04211c123 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -39,13 +39,15 @@ typedef struct GDateTime *date_start; GDateTime *date_end; GcalRecurrence *recur; + GcalTimeFormat time_format; } GcalScheduleValues; gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self); gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self); -GcalScheduleValues *gcal_schedule_values_from_event (GcalEvent *event); +GcalScheduleValues *gcal_schedule_values_from_event (GcalEvent *event, + GcalTimeFormat time_format); void gcal_schedule_values_free (GcalScheduleValues *values); G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleValues, gcal_schedule_values_free); -- GitLab From cdc602bb1f9debb2d445e00f030965465aadb565 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 27 Feb 2025 11:15:03 -0600 Subject: [PATCH 18/63] Start sketching gcal_schedule_values_set_start_date() But we need a test first! Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index fca37d5aa..5cac46158 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -139,6 +139,21 @@ gcal_schedule_values_set_time_format (const GcalScheduleValues *values, GcalTime return copy; } +/* The start_date_row widget has changed. We need to sync it to the values and + * adjust the other values based on it. + */ +static GcalScheduleValues * +gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime *t) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + /* move from on_start_date_changed_cb */ + + /* copy t to date_start */ + + /* the conditional is to adjust in case start > end. Write a test for that. */ +} + static WidgetState * widget_state_from_values (const GcalScheduleValues *values) { -- GitLab From a0a817ee8a82ce959d2f5b3d8e771bcb17b2d54a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 27 Feb 2025 11:29:46 -0600 Subject: [PATCH 19/63] Add scaffolding for a new test-internals binary * There is a gcal_tests_add_internal() function... * called from tests/test-internals.c... * ... which doesn't add any tests yet, but we'll have some soon. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 6 ++++++ src/gui/event-editor/gcal-schedule-section.h | 8 ++++++++ src/gui/gcal-tests.c | 15 +++++++++++++++ src/gui/gcal-tests.h | 3 +++ src/gui/meson.build | 1 + tests/meson.build | 1 + tests/test-internals.c | 17 +++++++++++++++++ 7 files changed, 51 insertions(+) create mode 100644 src/gui/gcal-tests.c create mode 100644 src/gui/gcal-tests.h create mode 100644 tests/test-internals.c diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 5cac46158..df13904db 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -949,3 +949,9 @@ gcal_schedule_values_free (GcalScheduleValues *values) g_free (values); } + +void +gcal_schedule_section_add_tests (void) +{ + /* g_test_add_func ("/event_editor/schedule_section/...", ...); */ +} diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index 04211c123..35ec0a462 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -23,6 +23,10 @@ #include #include +#include "gcal-enums.h" +#include "gcal-event.h" +#include "gcal-recurrence.h" + G_BEGIN_DECLS #define GCAL_TYPE_SCHEDULE_SECTION (gcal_schedule_section_get_type()) @@ -52,4 +56,8 @@ void gcal_schedule_values_free (GcalScheduleVa G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleValues, gcal_schedule_values_free); +/* Tests */ + +void gcal_schedule_section_add_tests (void); + G_END_DECLS diff --git a/src/gui/gcal-tests.c b/src/gui/gcal-tests.c new file mode 100644 index 000000000..979ba9fcb --- /dev/null +++ b/src/gui/gcal-tests.c @@ -0,0 +1,15 @@ +#include + +#include "gcal-tests.h" +#include "event-editor/gcal-schedule-section.h" + +/* Adds all the tests for the internals of the GUI. + * + * To avoid exporting lots of little test functions from each source file, add a single + * gcal_foo_add_tests() that in turn adds unit tests for that file. + */ +void +gcal_tests_add_internals (void) +{ + gcal_schedule_section_add_tests (); +} diff --git a/src/gui/gcal-tests.h b/src/gui/gcal-tests.h new file mode 100644 index 000000000..346d13fc9 --- /dev/null +++ b/src/gui/gcal-tests.h @@ -0,0 +1,3 @@ +#pragma once + +void gcal_tests_add_internals (void); diff --git a/src/gui/meson.build b/src/gui/meson.build index 7fbebd2c2..aff98194f 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -52,6 +52,7 @@ sources += files( 'gcal-quick-add-popover.c', 'gcal-search-button.c', 'gcal-sync-indicator.c', + 'gcal-tests.c', 'gcal-weather-settings.c', 'gcal-window.c', ) diff --git a/tests/meson.build b/tests/meson.build index 29aff0a20..682fff569 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -35,6 +35,7 @@ tests = [ 'daylight-saving', #'discoverer', # https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/1251 'event', + 'internals', 'range', 'range-tree', #'server', # https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/1251 diff --git a/tests/test-internals.c b/tests/test-internals.c new file mode 100644 index 000000000..239e569c9 --- /dev/null +++ b/tests/test-internals.c @@ -0,0 +1,17 @@ +#include +#include + +#include "gui/gcal-tests.h" + +int +main (int argc, char **argv) +{ + g_setenv ("TZ", "UTC", TRUE); + + g_test_init (&argc, &argv, NULL); + g_test_bug_base ("https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/"); + + gcal_tests_add_internals (); + + return g_test_run (); +} -- GitLab From 923424acab7213a431cff7fb8753c0b6305a7862 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 27 Feb 2025 11:30:51 -0600 Subject: [PATCH 20/63] Run the tests for the flatpak build Part-of: --- build-aux/org.gnome.Calendar.json | 1 + 1 file changed, 1 insertion(+) diff --git a/build-aux/org.gnome.Calendar.json b/build-aux/org.gnome.Calendar.json index f19d23c31..a812c11f3 100644 --- a/build-aux/org.gnome.Calendar.json +++ b/build-aux/org.gnome.Calendar.json @@ -166,6 +166,7 @@ { "name" : "gnome-calendar", "buildsystem" : "meson", + "run-tests": true, "sources" : [ { "type" : "dir", -- GitLab From 1309bf4a8206b69fc47324c94cf6e16c7fe4d2a1 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 18:19:07 -0600 Subject: [PATCH 21/63] gcal_schedule_section_set_event(): Don't change the timezones when setting up the widgets I think this was being done so that: - create a new all-day event - open it in the event editor - switch to time-slot - time choosers now say "UTC" doesn't happen. I think this can be handled more gracefully. Evolution does not handle it very gracefully; when turning off all-day in its event editor, it sets the times to $now. The iOS calendar app is better. If you turn all-day off, it picks $next_hour_based_on_now for the start time, and $next_hour_plus_one for the end time. So if you turn off all-day and it is 18:24 right now, it will set the start_time to 19:00 and the end_time to 20:00 --- that is, it creates a one-hour event at the next start of the hour from now. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 36 +++----------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index df13904db..1dc26dc57 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -445,10 +445,6 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalEvent *event, GcalEventEditorFlags flags) { - g_autoptr (GDateTime) date_time_start_utc = NULL; - g_autoptr (GDateTime) date_time_end_utc = NULL; - g_autoptr (GDateTime) date_time_start_local = NULL; - g_autoptr (GDateTime) date_time_end_local = NULL; GcalScheduleSection *self; GDateTime* date_time_start = NULL; GDateTime* date_time_end = NULL; @@ -488,41 +484,17 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, */ date_time_end = all_day ? g_date_time_add_days (date_time_end, -1) : date_time_end; - date_time_start_utc = g_date_time_new_utc (g_date_time_get_year (date_time_start), - g_date_time_get_month (date_time_start), - g_date_time_get_day_of_month (date_time_start), - 0, 0, 0); - - date_time_end_utc = g_date_time_new_utc (g_date_time_get_year (date_time_end), - g_date_time_get_month (date_time_end), - g_date_time_get_day_of_month (date_time_end), - 0, 0, 0); - - date_time_start_local = g_date_time_new_local (g_date_time_get_year (date_time_start), - g_date_time_get_month (date_time_start), - g_date_time_get_day_of_month (date_time_start), - g_date_time_get_hour (date_time_start), - g_date_time_get_minute (date_time_start), - 0); - - date_time_end_local = g_date_time_new_local (g_date_time_get_year (date_time_end), - g_date_time_get_month (date_time_end), - g_date_time_get_day_of_month (date_time_end), - g_date_time_get_hour (date_time_end), - g_date_time_get_minute (date_time_end), - 0); - block_date_signals (self); /* date */ - gcal_date_chooser_row_set_date (self->start_date_row, date_time_start_utc); - gcal_date_chooser_row_set_date (self->end_date_row, date_time_end_utc); + gcal_date_chooser_row_set_date (self->start_date_row, date_time_start); + gcal_date_chooser_row_set_date (self->end_date_row, date_time_end); /* date-time */ if (new_event || all_day) { - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start_local); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end_local); + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); } else { -- GitLab From 2ca29785dc4ffa77c4cbc3f5a1bfb5f4f142d9f3 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 18:36:33 -0600 Subject: [PATCH 22/63] Remove superfluous conditional Now that all cases set the same times to the {start,end}_date_time_chooser widgets, we don't need that condition anymore. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 1dc26dc57..4e33a696b 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -491,16 +491,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, gcal_date_chooser_row_set_date (self->end_date_row, date_time_end); /* date-time */ - if (new_event || all_day) - { - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); - } - else - { - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); - } + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); unblock_date_signals (self); -- GitLab From ba69d044652e319c8799fbfa2867e9408aa6d3e2 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 18:37:32 -0600 Subject: [PATCH 23/63] Remove unused variable Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 4e33a696b..49e000c52 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -450,7 +450,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GDateTime* date_time_end = NULL; GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; - gboolean all_day, new_event; + gboolean all_day; GcalTimeFormat time_format; GcalScheduleValues *values; g_autoptr (WidgetState) state = NULL; @@ -470,7 +470,6 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GCAL_RETURN (); all_day = values->all_day; - new_event = flags & GCAL_EVENT_EDITOR_FLAG_NEW_EVENT; state = widget_state_from_values (values); update_widgets (self, state); -- GitLab From 1cfb58e3c433667a2ecdff0f343a2844b6039541 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 18:47:26 -0600 Subject: [PATCH 24/63] Fix leak of date_time_end It's replaced by this line: date_time_end = all_day ? g_date_time_add_days (date_time_end, -1) : date_time_end; so we need to be able to free that. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 49e000c52..b370e0eb6 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -447,7 +447,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, { GcalScheduleSection *self; GDateTime* date_time_start = NULL; - GDateTime* date_time_end = NULL; + g_autoptr (GDateTime) date_time_end = NULL; GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; gboolean all_day; @@ -476,7 +476,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, /* retrieve start and end date-times */ date_time_start = values->date_start; - date_time_end = values->date_end; + date_time_end = g_date_time_ref (values->date_end); /* * This is subtracting what has been added in action_button_clicked (). * See bug 769300. -- GitLab From f787afbeab3426eaba24ae9fe1c4b4cee4593254 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 19:10:48 -0600 Subject: [PATCH 25/63] Clarify oudated comments Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index b370e0eb6..5ae6d4f4e 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -477,9 +477,13 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, /* retrieve start and end date-times */ date_time_start = values->date_start; date_time_end = g_date_time_ref (values->date_end); - /* - * This is subtracting what has been added in action_button_clicked (). - * See bug 769300. + + /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't want + * to show a single-day all-day event as starting on Feb 1 and ending on Feb 2, for + * example. So, we subtract a day from the end date to show the expected thing to the + * user. We restore this when setting the date-time back on the event. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. */ date_time_end = all_day ? g_date_time_add_days (date_time_end, -1) : date_time_end; @@ -573,13 +577,13 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, gcal_event_set_all_day (event, all_day); - /* - * The end date for multi-day events is exclusive, so we bump it by a day. - * This fixes the discrepancy between the end day of the event and how it - * is displayed in the month view. See bug 769300. - */ if (all_day) { + /* See the comment "While in iCalendar" elsewhere in this file. Here we restore the + * correct date-time for the event. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. + */ GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); end_date = fake_end_date; -- GitLab From 8120162f236f4a6aabd233b01371d9855549b746 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 19:11:04 -0600 Subject: [PATCH 26/63] Fix leak of two GDateTime While gcal_date_time_chooser_get_date_time() returns a new reference, gcal_date_chooser_row_get_date() just returns the pointer to the date being edited. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 5ae6d4f4e..10a5554fd 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -546,7 +546,8 @@ static void gcal_schedule_section_apply_to_event (GcalScheduleSection *self, GcalEvent *event) { - GDateTime *start_date, *end_date; + g_autoptr (GDateTime) start_date = NULL; + g_autoptr (GDateTime) end_date = NULL; GcalRecurrenceFrequency freq; gboolean all_day; @@ -561,8 +562,8 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, } else { - start_date = gcal_date_chooser_row_get_date (self->start_date_row); - end_date = gcal_date_chooser_row_get_date (self->end_date_row); + start_date = g_date_time_ref (gcal_date_chooser_row_get_date (self->start_date_row)); + end_date = g_date_time_ref (gcal_date_chooser_row_get_date (self->end_date_row)); } #ifdef GCAL_ENABLE_TRACE -- GitLab From e0641709bf733b58ed14e05fbdc7b04445be9150 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 28 Feb 2025 20:29:55 -0600 Subject: [PATCH 27/63] Move handling of the start/end dates to GcalScheduleValues / WidgetState This clarifies things: * GcalScheduleValues contains the real data, based initially on the GcalEvent. * WidgetState's date_time_start / date_time_end may not be the same as the real data, since the values get adjusted for display, as in the case when handling all_day events. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 68 +++++++++++--------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 10a5554fd..b640e8894 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -69,6 +69,12 @@ typedef struct gboolean date_widgets_visible; gboolean date_time_widgets_visible; GcalTimeFormat time_format; + + /* Note that the displayed date/time may not correspond to the real data in the + * GcalScheduleValues. See the comment in widget_state_from_values(). + */ + GDateTime *date_time_start; + GDateTime *date_time_end; } WidgetState; static void widget_state_free (WidgetState *state); @@ -164,12 +170,34 @@ widget_state_from_values (const GcalScheduleValues *values) state->date_time_widgets_visible = !values->all_day; state->time_format = values->time_format; + g_assert (values->date_start); + state->date_time_start = g_date_time_ref (values->date_start); + + if (values->all_day) + { + /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't want + * to show a single-day all-day event as starting on Feb 1 and ending on Feb 2, for + * example. So, we subtract a day from the end date to show the expected thing to the + * user. We restore this when setting the date-time back on the event. + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. + */ + state->date_time_end = g_date_time_add_days (values->date_end, -1); + } + else + { + g_assert (values->date_end); + state->date_time_end = g_date_time_ref (values->date_end); + } + return state; } static void widget_state_free (WidgetState *state) { + g_date_time_unref (state->date_time_start); + g_date_time_unref (state->date_time_end); g_free (state); } @@ -222,6 +250,7 @@ update_widgets (GcalScheduleSection *self, WidgetState *state) { g_signal_handlers_block_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); + block_date_signals (self); adw_toggle_group_set_active_name (self->schedule_type_toggle_group, state->schedule_type_all_day ? "all-day" : "time-slot"); @@ -234,6 +263,15 @@ update_widgets (GcalScheduleSection *self, gcal_date_time_chooser_set_time_format (self->start_date_time_chooser, state->time_format); gcal_date_time_chooser_set_time_format (self->end_date_time_chooser, state->time_format); + /* date */ + gcal_date_chooser_row_set_date (self->start_date_row, state->date_time_start); + gcal_date_chooser_row_set_date (self->end_date_row, state->date_time_end); + + /* date-time */ + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, state->date_time_start); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, state->date_time_end); + + unblock_date_signals (self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } @@ -446,11 +484,8 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalEventEditorFlags flags) { GcalScheduleSection *self; - GDateTime* date_time_start = NULL; - g_autoptr (GDateTime) date_time_end = NULL; GcalRecurrenceLimitType limit_type; GcalRecurrenceFrequency frequency; - gboolean all_day; GcalTimeFormat time_format; GcalScheduleValues *values; g_autoptr (WidgetState) state = NULL; @@ -469,36 +504,9 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, if (!event) GCAL_RETURN (); - all_day = values->all_day; - state = widget_state_from_values (values); update_widgets (self, state); - /* retrieve start and end date-times */ - date_time_start = values->date_start; - date_time_end = g_date_time_ref (values->date_end); - - /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't want - * to show a single-day all-day event as starting on Feb 1 and ending on Feb 2, for - * example. So, we subtract a day from the end date to show the expected thing to the - * user. We restore this when setting the date-time back on the event. - * - * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. - */ - date_time_end = all_day ? g_date_time_add_days (date_time_end, -1) : date_time_end; - - block_date_signals (self); - - /* date */ - gcal_date_chooser_row_set_date (self->start_date_row, date_time_start); - gcal_date_chooser_row_set_date (self->end_date_row, date_time_end); - - /* date-time */ - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, date_time_start); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, date_time_end); - - unblock_date_signals (self); - /* Recurrences */ if (values->recur) { -- GitLab From 18beea3052b1f1688b30ad1e61084babb8109c67 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 15:03:37 -0600 Subject: [PATCH 28/63] Extract function to update the widgets *and* the values Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index b640e8894..8a99aaa89 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -275,6 +275,22 @@ update_widgets (GcalScheduleSection *self, g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } +/* Updates the widgets, and the current values, from the specified ones. + * + * This will free the current values and replace them with new ones. + */ +static void +update_from_values (GcalScheduleSection *self, + GcalScheduleValues *values) +{ + g_autoptr (WidgetState) state = widget_state_from_values (values); + + update_widgets (self, state); + + gcal_schedule_values_free (self->values); + self->values = values; +} + /* * Callbacks */ @@ -286,12 +302,10 @@ on_schedule_type_changed_cb (GtkWidget *widget, { gboolean all_day = all_day_selected (self); GcalScheduleValues *updated = gcal_schedule_values_set_all_day (self->values, all_day); - g_autoptr (WidgetState) state = widget_state_from_values (updated); + update_from_values (self, updated); block_date_signals (self); - update_widgets (self, state); - if (all_day) { g_autoptr (GDateTime) start_local = NULL; -- GitLab From 9975a9692793942e4220f230a4b73266d15137c4 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 19:09:40 -0600 Subject: [PATCH 29/63] Add a test, with the failing part commented out I want to test that when: - a one-hour event is loaded - all-day is turned on then the start and end date widgets display both the same date. This fails right now, because we subtract one from the end_date. I'll change the way the dates are handled so that this magic only needs to be done when setting back the GcalEvent. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 42 +++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 8a99aaa89..eb05160c2 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -940,8 +940,48 @@ gcal_schedule_values_free (GcalScheduleValues *values) g_free (values); } +static void +test_turning_all_day_on_displays_sensible_dates (void) +{ + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T12:34:56-06:00", NULL); + g_assert (date != NULL); + + g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T13:34:56-06:00", NULL); + g_assert (one_hour_later != NULL); + + g_autoptr (GcalScheduleValues) values = g_new0 (GcalScheduleValues, 1); + + *values = (GcalScheduleValues) { + .all_day = FALSE, + .date_start = g_date_time_ref (date), + .date_end = g_date_time_ref (one_hour_later), + .recur = NULL, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_all_day (values, TRUE); + + g_autoptr (WidgetState) state = widget_state_from_values (new_values); + + g_assert_true (state->schedule_type_all_day); + g_assert_true (state->date_widgets_visible); + g_assert_false (state->date_time_widgets_visible); + + g_assert_cmpint (g_date_time_get_year (state->date_time_start), ==, 2025); + g_assert_cmpint (g_date_time_get_month (state->date_time_start), ==, 3); + g_assert_cmpint (g_date_time_get_day_of_month (state->date_time_start), ==, 3); + + g_assert_cmpint (g_date_time_get_year (state->date_time_end), ==, 2025); + g_assert_cmpint (g_date_time_get_month (state->date_time_end), ==, 3); + /* test failure: + * g_assert_cmpint (g_date_time_get_day_of_month (state->date_time_end), ==, 3); + * this is because we subtract a day from the all_day end_date + */ +} + void gcal_schedule_section_add_tests (void) { - /* g_test_add_func ("/event_editor/schedule_section/...", ...); */ + g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", + test_turning_all_day_on_displays_sensible_dates); } -- GitLab From 481569543da0e958c95ae616e1ae80d81b6bd8c5 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 19:12:09 -0600 Subject: [PATCH 30/63] on_schedule_type_changed_cb(): Don't frob the widgets here Converting the start/end dates to local time is done to get them to display as the user expects. Instead, let's change things so that they are actually computed and handled correctly. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index eb05160c2..17eed5584 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -303,27 +303,6 @@ on_schedule_type_changed_cb (GtkWidget *widget, gboolean all_day = all_day_selected (self); GcalScheduleValues *updated = gcal_schedule_values_set_all_day (self->values, all_day); update_from_values (self, updated); - - block_date_signals (self); - - if (all_day) - { - g_autoptr (GDateTime) start_local = NULL; - g_autoptr (GDateTime) end_local = NULL; - - GDateTime *start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - GDateTime *end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - - start_local = g_date_time_to_local (start); - end_local = g_date_time_to_local (end); - - gcal_date_chooser_row_set_date (self->start_date_row, start_local); - gcal_date_chooser_row_set_date (self->end_date_row, end_local); - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, start_local); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, end_local); - } - - unblock_date_signals (self); } static void -- GitLab From ba3380206d471ad233df47457b5c1ff884129914 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 19:26:42 -0600 Subject: [PATCH 31/63] GcalScheduleValues: keep copies of the original dates of the event That is, we'll keep the original values, plus the working values that get changed from the widgets. We'll use the original values to be able to reconstruct the original event's duration if needed. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 12 ++++++++++-- src/gui/event-editor/gcal-schedule-section.h | 8 ++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 17eed5584..ab9c16688 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -119,6 +119,8 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) GcalScheduleValues *copy = g_new0 (GcalScheduleValues, 1); copy->all_day = values->all_day; + copy->orig_date_start = values->orig_date_start ? g_date_time_ref (values->orig_date_start) : NULL; + copy->orig_date_end = values->orig_date_end ? g_date_time_ref (values->orig_date_end) : NULL; copy->date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; copy->date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; copy->recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; @@ -889,8 +891,12 @@ gcal_schedule_values_from_event (GcalEvent *event, GcalRecurrence *recur = gcal_event_get_recurrence (event); values->all_day = gcal_event_get_all_day (event); - values->date_start = g_date_time_ref (gcal_event_get_date_start (event)); - values->date_end = g_date_time_ref (gcal_event_get_date_end (event)); + + values->orig_date_start = g_date_time_ref (gcal_event_get_date_start (event)); + values->date_start = g_date_time_ref (values->orig_date_start); + + values->orig_date_end = g_date_time_ref (gcal_event_get_date_end (event)); + values->date_end = g_date_time_ref (values->orig_date_end); if (recur) { @@ -932,6 +938,8 @@ test_turning_all_day_on_displays_sensible_dates (void) *values = (GcalScheduleValues) { .all_day = FALSE, + .orig_date_start = g_date_time_ref (date), + .orig_date_end = g_date_time_ref (one_hour_later), .date_start = g_date_time_ref (date), .date_end = g_date_time_ref (one_hour_later), .recur = NULL, diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index 35ec0a462..fda4cedd7 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -40,8 +40,16 @@ G_DECLARE_FINAL_TYPE (GcalScheduleSection, gcal_schedule_section, GCAL, SCHEDULE typedef struct { gboolean all_day; + + /* Original times from the GcalEvent. We keep these around to be able to reconstruct + * the event's duration in case the all-day toggle gets turned on and off repeatedly. + */ + GDateTime *orig_date_start; + GDateTime *orig_date_end; + GDateTime *date_start; GDateTime *date_end; + GcalRecurrence *recur; GcalTimeFormat time_format; } GcalScheduleValues; -- GitLab From af4ddc12829b9f74f758992ebace4505e9923b4b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 19:52:04 -0600 Subject: [PATCH 32/63] Move the logic from on_start_date_changed_cb() to gcal_schedule_values_set_start_date() This also adds a test for syncing the date_end to the date_start if the date_start happens to be set after the date_end. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 73 +++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index ab9c16688..58821293d 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -151,15 +151,18 @@ gcal_schedule_values_set_time_format (const GcalScheduleValues *values, GcalTime * adjust the other values based on it. */ static GcalScheduleValues * -gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime *t) +gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime *start) { GcalScheduleValues *copy = gcal_schedule_values_copy (values); - /* move from on_start_date_changed_cb */ + gcal_set_date_time (©->date_start, start); - /* copy t to date_start */ + if (g_date_time_compare (start, copy->date_end) == 1) + { + gcal_set_date_time (©->date_end, start); + } - /* the conditional is to adjust in case start > end. Write a test for that. */ + return copy; } static WidgetState * @@ -312,27 +315,9 @@ on_start_date_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GDateTime *start, *end; - - GCAL_ENTRY; - - block_date_signals (self); - - start = gcal_date_chooser_row_get_date (self->start_date_row); - end = gcal_date_chooser_row_get_date (self->end_date_row); - - if (g_date_time_compare (start, end) == 1) - { - gcal_date_chooser_row_set_date (self->end_date_row, start); - gcal_date_time_chooser_set_date (self->end_date_time_chooser, start); - } - - // Keep the date row and the date-time chooser in sync - gcal_date_time_chooser_set_date (self->start_date_time_chooser, start); - - unblock_date_signals (self); - - GCAL_EXIT; + GDateTime *start = gcal_date_chooser_row_get_date (self->start_date_row); + GcalScheduleValues *updated = gcal_schedule_values_set_start_date (self->values, start); + update_from_values (self, updated); } static void @@ -925,6 +910,42 @@ gcal_schedule_values_free (GcalScheduleValues *values) g_free (values); } +static void +test_setting_start_date_after_end_date_resets_end_date (void) +{ + /* We start with + * start = 10:00 + * end = 11:00 + * + * Then we set start to 12:00 + * + * We want to test that end becomes 12:00 as well. + */ + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T10:00:00-06:00", NULL); + g_assert (date != NULL); + + g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T11:00:00-06:00", NULL); + g_assert (one_hour_later != NULL); + + g_autoptr (GcalScheduleValues) values = g_new (GcalScheduleValues, 1); + + *values = (GcalScheduleValues) { + .all_day = FALSE, + .orig_date_start = g_date_time_ref (date), + .orig_date_end = g_date_time_ref (one_hour_later), + .date_start = g_date_time_ref (date), + .date_end = g_date_time_ref (one_hour_later), + .recur = NULL, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); + + g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_start_date (values, two_hours_later); + g_assert (g_date_time_equal (new_values->date_start, two_hours_later)); + g_assert (g_date_time_equal (new_values->date_end, two_hours_later)); +} + static void test_turning_all_day_on_displays_sensible_dates (void) { @@ -969,6 +990,8 @@ test_turning_all_day_on_displays_sensible_dates (void) void gcal_schedule_section_add_tests (void) { + g_test_add_func ("/event_editor/schedule_section/setting_start_date_after_end_date_resets_end_date", + test_setting_start_date_after_end_date_resets_end_date); g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", test_turning_all_day_on_displays_sensible_dates); } -- GitLab From a7282564fde8033e87bd758a970db4a1ad257cd9 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 3 Mar 2025 21:13:39 -0600 Subject: [PATCH 33/63] Move the logic for setting the date_end from on_end_date_changed_cb() to gcal_schedule_values_set_end_date() Analogous to the last commit; also add a test. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 80 +++++++++++++++----- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 58821293d..04950f878 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -165,6 +165,24 @@ gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime return copy; } +/* The end_date_row widget has changed. We need to sync it to the values and + * adjust the other values based on it. + */ +static GcalScheduleValues * +gcal_schedule_values_set_end_date (const GcalScheduleValues *values, GDateTime *end) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + gcal_set_date_time (©->date_end, end); + + if (g_date_time_compare (copy->date_start, end) == 1) + { + gcal_set_date_time (©->date_start, end); + } + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleValues *values) { @@ -325,27 +343,9 @@ on_end_date_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GDateTime *start, *end; - - GCAL_ENTRY; - - block_date_signals (self); - - start = gcal_date_chooser_row_get_date (self->start_date_row); - end = gcal_date_chooser_row_get_date (self->end_date_row); - - if (g_date_time_compare (start, end) == 1) - { - gcal_date_chooser_row_set_date (self->start_date_row, end); - gcal_date_time_chooser_set_date (self->start_date_time_chooser, end); - } - - // Keep the date row and the date-time chooser in sync - gcal_date_time_chooser_set_date (self->end_date_time_chooser, end); - - unblock_date_signals (self); - - GCAL_EXIT; + GDateTime *end = gcal_date_chooser_row_get_date (self->end_date_row); + GcalScheduleValues *updated = gcal_schedule_values_set_end_date (self->values, end); + update_from_values (self, updated); } static void @@ -946,6 +946,42 @@ test_setting_start_date_after_end_date_resets_end_date (void) g_assert (g_date_time_equal (new_values->date_end, two_hours_later)); } +static void +test_setting_end_date_before_start_date_resets_start_date (void) +{ + /* We start with + * start = 10:00 + * end = 11:00 + * + * Then we set end to 09:00 + * + * We want to test that start becomes 09:00 as well. + */ + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T10:00:00-06:00", NULL); + g_assert (date != NULL); + + g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T11:00:00-06:00", NULL); + g_assert (one_hour_later != NULL); + + g_autoptr (GcalScheduleValues) values = g_new (GcalScheduleValues, 1); + + *values = (GcalScheduleValues) { + .all_day = FALSE, + .orig_date_start = g_date_time_ref (date), + .orig_date_end = g_date_time_ref (one_hour_later), + .date_start = g_date_time_ref (date), + .date_end = g_date_time_ref (one_hour_later), + .recur = NULL, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); + + g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_end_date (values, two_hours_earlier); + g_assert (g_date_time_equal (new_values->date_start, two_hours_earlier)); + g_assert (g_date_time_equal (new_values->date_end, two_hours_earlier)); +} + static void test_turning_all_day_on_displays_sensible_dates (void) { @@ -992,6 +1028,8 @@ gcal_schedule_section_add_tests (void) { g_test_add_func ("/event_editor/schedule_section/setting_start_date_after_end_date_resets_end_date", test_setting_start_date_after_end_date_resets_end_date); + g_test_add_func ("/event_editor/schedule_section/setting_end_date_before_start_date_resets_start_date", + test_setting_end_date_before_start_date_resets_start_date); g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", test_turning_all_day_on_displays_sensible_dates); } -- GitLab From 6029b5f0613682e1924e409ad06afbcfa4aeb272 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 11:18:26 -0600 Subject: [PATCH 34/63] Extract function to create a GcalScheduleValues with certain date-time for tests Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 63 +++++++++----------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 04950f878..10301cbe7 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -910,6 +910,31 @@ gcal_schedule_values_free (GcalScheduleValues *values) g_free (values); } +/* Builds a new Gcalschedulevalues from two ISO 8601 date-times; be sure to include your timezone if you need it */ +static GcalScheduleValues * +values_with_date_times(const char *start, const char *end) +{ + g_autoptr (GDateTime) start_date = g_date_time_new_from_iso8601 (start, NULL); + g_assert (start_date != NULL); + + g_autoptr (GDateTime) end_date = g_date_time_new_from_iso8601 (end, NULL); + g_assert (end_date != NULL); + + GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); + + *values = (GcalScheduleValues) { + .all_day = FALSE, + .orig_date_start = g_date_time_ref (start_date), + .orig_date_end = g_date_time_ref (end_date), + .date_start = g_date_time_ref (start_date), + .date_end = g_date_time_ref (end_date), + .recur = NULL, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + return values; +} + static void test_setting_start_date_after_end_date_resets_end_date (void) { @@ -921,23 +946,8 @@ test_setting_start_date_after_end_date_resets_end_date (void) * * We want to test that end becomes 12:00 as well. */ - g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T10:00:00-06:00", NULL); - g_assert (date != NULL); - - g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T11:00:00-06:00", NULL); - g_assert (one_hour_later != NULL); - - g_autoptr (GcalScheduleValues) values = g_new (GcalScheduleValues, 1); - - *values = (GcalScheduleValues) { - .all_day = FALSE, - .orig_date_start = g_date_time_ref (date), - .orig_date_end = g_date_time_ref (one_hour_later), - .date_start = g_date_time_ref (date), - .date_end = g_date_time_ref (one_hour_later), - .recur = NULL, - .time_format = GCAL_TIME_FORMAT_24H, - }; + g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00"); g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); @@ -957,23 +967,8 @@ test_setting_end_date_before_start_date_resets_start_date (void) * * We want to test that start becomes 09:00 as well. */ - g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T10:00:00-06:00", NULL); - g_assert (date != NULL); - - g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T11:00:00-06:00", NULL); - g_assert (one_hour_later != NULL); - - g_autoptr (GcalScheduleValues) values = g_new (GcalScheduleValues, 1); - - *values = (GcalScheduleValues) { - .all_day = FALSE, - .orig_date_start = g_date_time_ref (date), - .orig_date_end = g_date_time_ref (one_hour_later), - .date_start = g_date_time_ref (date), - .date_end = g_date_time_ref (one_hour_later), - .recur = NULL, - .time_format = GCAL_TIME_FORMAT_24H, - }; + g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00"); g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); -- GitLab From cf43f6e475280d2bf1791b9dc477e067b084013d Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 12:20:30 -0600 Subject: [PATCH 35/63] Add a correctness check to the helper function I was writing a new test, and messed up the times vs. timezones of my start/end times. So, let's check them so it is easier to write new tests. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 10301cbe7..4268198ec 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -920,6 +920,8 @@ values_with_date_times(const char *start, const char *end) g_autoptr (GDateTime) end_date = g_date_time_new_from_iso8601 (end, NULL); g_assert (end_date != NULL); + g_assert (g_date_time_compare (start_date, end_date) == -1); + GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); *values = (GcalScheduleValues) { -- GitLab From 1ace5806d3aad2a3c83926b71ae40fba5b52e405 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 11:18:45 -0600 Subject: [PATCH 36/63] Move the logic from on_start_date_time_changed_cb() to gcal_schedule_values_set_start_date_time() If the new start_date is later than the end_date, we make the end_date the same as the new start_date, but with the original end_date's timezone. And add a test. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 71 ++++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 4268198ec..8866d810f 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -183,6 +183,25 @@ gcal_schedule_values_set_end_date (const GcalScheduleValues *values, GDateTime * return copy; } +static GcalScheduleValues * +gcal_schedule_values_set_start_date_time (const GcalScheduleValues *values, GDateTime *start) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + gcal_set_date_time (©->date_start, start); + + if (g_date_time_compare (start, copy->date_end) == 1) + { + GTimeZone *end_tz = g_date_time_get_timezone (copy->date_end); + g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); + g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); + + gcal_set_date_time (©->date_end, new_end_in_end_tz); + } + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleValues *values) { @@ -353,27 +372,9 @@ on_start_date_time_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GDateTime *start, *end; - - GCAL_ENTRY; - - block_date_signals (self); - - start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - - if (g_date_time_compare (start, end) == 1) - { - GTimeZone *end_tz = g_date_time_get_timezone (end); - g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); - g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); - - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, new_end_in_end_tz); - } - - unblock_date_signals (self); - - GCAL_EXIT; + GDateTime *start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); + GcalScheduleValues *updated = gcal_schedule_values_set_start_date_time (self->values, start); + update_from_values (self, updated); } static void @@ -979,6 +980,32 @@ test_setting_end_date_before_start_date_resets_start_date (void) g_assert (g_date_time_equal (new_values->date_end, two_hours_earlier)); } +static void +test_setting_start_datetime_preserves_end_timezone (void) +{ + /* Start with + * start = 10:00, UTC-6 + * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) + * + * Set the start date to 11:30, UTC-6 + * + * End date should be 13:30, UTC-5 (i.e. end_date was set to the same as start_date, but in a different tz) + * + * (imagine driving for one hour while crossing timezones) + */ + g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00"); + g_assert (g_date_time_compare (values->date_start, values->date_end) == -1); + + g_autoptr (GDateTime) new_start = g_date_time_new_from_iso8601 ("20250303T11:30:00-06:00", NULL); + g_autoptr (GDateTime) expected_end = g_date_time_new_from_iso8601 ("20250303T13:30:00-05:00", NULL); + + g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_start_date_time (values, new_start); + g_assert (g_date_time_equal (new_values->date_start, new_start)); + + g_assert (g_date_time_equal (new_values->date_end, expected_end)); +} + static void test_turning_all_day_on_displays_sensible_dates (void) { @@ -1027,6 +1054,8 @@ gcal_schedule_section_add_tests (void) test_setting_start_date_after_end_date_resets_end_date); g_test_add_func ("/event_editor/schedule_section/setting_end_date_before_start_date_resets_start_date", test_setting_end_date_before_start_date_resets_start_date); + g_test_add_func ("/event_editor/schedule_section/setting_start_datetime_preserves_end_timezone", + test_setting_start_datetime_preserves_end_timezone); g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", test_turning_all_day_on_displays_sensible_dates); } -- GitLab From 6f19c32a80c208a1352ab5d3996c6980b1f4a75b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 13:51:02 -0600 Subject: [PATCH 37/63] Move the logic from on_end_date_time_changed_cb() to gcal_schedule_values_set_end_date_time() And add a test. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 71 ++++++++++++++------ 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 8866d810f..d7979a437 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -202,6 +202,25 @@ gcal_schedule_values_set_start_date_time (const GcalScheduleValues *values, GDat return copy; } +static GcalScheduleValues * +gcal_schedule_values_set_end_date_time (const GcalScheduleValues *values, GDateTime *end) +{ + GcalScheduleValues *copy = gcal_schedule_values_copy (values); + + gcal_set_date_time (©->date_end, end); + + if (g_date_time_compare (copy->date_start, end) == 1) + { + GTimeZone *start_tz = g_date_time_get_timezone (copy->date_start); + g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); + g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); + + gcal_set_date_time (©->date_start, new_start_in_start_tz); + } + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleValues *values) { @@ -382,27 +401,9 @@ on_end_date_time_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GDateTime *start, *end; - - GCAL_ENTRY; - - block_date_signals (self); - - start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - - if (g_date_time_compare (start, end) == 1) - { - GTimeZone *start_tz = g_date_time_get_timezone (start); - g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); - g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); - - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, new_start_in_start_tz); - } - - unblock_date_signals (self); - - GCAL_EXIT; + GDateTime *end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); + GcalScheduleValues *updated = gcal_schedule_values_set_end_date_time (self->values, end); + update_from_values (self, updated); } static void @@ -1006,6 +1007,32 @@ test_setting_start_datetime_preserves_end_timezone (void) g_assert (g_date_time_equal (new_values->date_end, expected_end)); } +static void +test_setting_end_datetime_preserves_start_timezone (void) +{ + /* Start with + * start = 10:00, UTC-6 + * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) + * + * Set the end date to 09:30, UTC-5 + * + * Start date should be 07:30, UTC-6 (set to one hour earlier than the end time, with the original start's tz) + * + * (imagine driving for one hour while crossing timezones) + */ + g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00"); + g_assert (g_date_time_compare (values->date_start, values->date_end) == -1); + + g_autoptr (GDateTime) new_end = g_date_time_new_from_iso8601 ("20250303T09:30:00-05:00", NULL); + g_autoptr (GDateTime) expected_start = g_date_time_new_from_iso8601 ("20250303T07:30:00-06:00", NULL); + + g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_end_date_time (values, new_end); + g_assert (g_date_time_equal (new_values->date_end, new_end)); + + g_assert (g_date_time_equal (new_values->date_start, expected_start)); +} + static void test_turning_all_day_on_displays_sensible_dates (void) { @@ -1056,6 +1083,8 @@ gcal_schedule_section_add_tests (void) test_setting_end_date_before_start_date_resets_start_date); g_test_add_func ("/event_editor/schedule_section/setting_start_datetime_preserves_end_timezone", test_setting_start_datetime_preserves_end_timezone); + g_test_add_func ("/event_editor/schedule_section/setting_end_datetime_preserves_start_timezone", + test_setting_end_datetime_preserves_start_timezone); g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", test_turning_all_day_on_displays_sensible_dates); } -- GitLab From 6c0da161a67d5659928055fd1db3712c38d47b5f Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 16:33:40 -0600 Subject: [PATCH 38/63] gcal_schedule_section_apply_to_event(): Get the data from GcalScheduleValues, not the widgets Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index d7979a437..53cd2022d 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -539,22 +539,11 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, g_autoptr (GDateTime) start_date = NULL; g_autoptr (GDateTime) end_date = NULL; GcalRecurrenceFrequency freq; - gboolean all_day; GCAL_ENTRY; - all_day = all_day_selected (self); - - if (!all_day) - { - start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - } - else - { - start_date = g_date_time_ref (gcal_date_chooser_row_get_date (self->start_date_row)); - end_date = g_date_time_ref (gcal_date_chooser_row_get_date (self->end_date_row)); - } + start_date = g_date_time_ref (self->values->date_start); + end_date = g_date_time_ref (self->values->date_end); #ifdef GCAL_ENABLE_TRACE { @@ -566,9 +555,9 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, } #endif - gcal_event_set_all_day (event, all_day); + gcal_event_set_all_day (event, self->values->all_day); - if (all_day) + if (self->values->all_day) { /* See the comment "While in iCalendar" elsewhere in this file. Here we restore the * correct date-time for the event. -- GitLab From f2d039487e447b7a25329b29ec527276dfe97c2c Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Tue, 4 Mar 2025 16:36:45 -0600 Subject: [PATCH 39/63] Fix leak Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 53cd2022d..a5873f0b2 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -566,7 +566,7 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, */ GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); - end_date = fake_end_date; + gcal_set_date_time (&end_date, fake_end_date); } gcal_event_set_date_start (event, start_date); -- GitLab From 20d593295f1af66067e44a7a3f0f22f3f6db9ef8 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Wed, 5 Mar 2025 21:40:17 -0600 Subject: [PATCH 40/63] For all-day, keep real dates in GcalScheduleValues, and human-readable dates in WidgetState This reorders the trick to "subtract one day from date_end" and "add it back when applying the data to the event". It also has a test for roundtripping. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 118 +++++++++++-------- 1 file changed, 67 insertions(+), 51 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index a5873f0b2..f6e23d309 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -173,7 +173,19 @@ gcal_schedule_values_set_end_date (const GcalScheduleValues *values, GDateTime * { GcalScheduleValues *copy = gcal_schedule_values_copy (values); - gcal_set_date_time (©->date_end, end); + if (copy->all_day) + { + /* See the comment "While in iCalendar..." in widget_state_from_values(). Here we + * take the human-readable end-date, and turn it into the appropriate value for + * iCalendar. + */ + g_autoptr (GDateTime) fake_end_date = g_date_time_add_days (end, 1); + gcal_set_date_time (©->date_end, fake_end_date); + } + else + { + gcal_set_date_time (©->date_end, end); + } if (g_date_time_compare (copy->date_start, end) == 1) { @@ -236,12 +248,17 @@ widget_state_from_values (const GcalScheduleValues *values) if (values->all_day) { - /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't want - * to show a single-day all-day event as starting on Feb 1 and ending on Feb 2, for - * example. So, we subtract a day from the end date to show the expected thing to the - * user. We restore this when setting the date-time back on the event. + /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't + * want to show the user a single-day all-day event as starting on Feb 1 and ending + * on Feb 2, for example. + * + * So, we maintain the "real" data in GcalScheduleValues with the $day+1 as iCalendar wants, + * but for display purposes, we subtract a day from the end date to show the expected thing to the + * user. When the widget changes, we add the day back - see gcal_schedule_values_set_end_date(). * * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. + * + * Keep in sync with gcal_schedule_values_set_end_date(). */ state->date_time_end = g_date_time_add_days (values->date_end, -1); } @@ -536,41 +553,12 @@ static void gcal_schedule_section_apply_to_event (GcalScheduleSection *self, GcalEvent *event) { - g_autoptr (GDateTime) start_date = NULL; - g_autoptr (GDateTime) end_date = NULL; GcalRecurrenceFrequency freq; GCAL_ENTRY; - start_date = g_date_time_ref (self->values->date_start); - end_date = g_date_time_ref (self->values->date_end); - -#ifdef GCAL_ENABLE_TRACE - { - g_autofree gchar *start_dt_string = g_date_time_format (start_date, "%x %X %z"); - g_autofree gchar *end_dt_string = g_date_time_format (end_date, "%x %X %z"); - - g_debug ("New start date: %s", start_dt_string); - g_debug ("New end date: %s", end_dt_string); - } -#endif - - gcal_event_set_all_day (event, self->values->all_day); - - if (self->values->all_day) - { - /* See the comment "While in iCalendar" elsewhere in this file. Here we restore the - * correct date-time for the event. - * - * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. - */ - GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); - - gcal_set_date_time (&end_date, fake_end_date); - } - - gcal_event_set_date_start (event, start_date); - gcal_event_set_date_end (event, end_date); + gcal_event_set_date_start (event, self->values->date_start); + gcal_event_set_date_end (event, self->values->date_end); /* Check Repeat popover and set recurrence-rules accordingly */ @@ -1023,29 +1011,45 @@ test_setting_end_datetime_preserves_start_timezone (void) } static void -test_turning_all_day_on_displays_sensible_dates (void) +test_all_day_displays_sensible_dates_and_roundtrips (void) { - g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T12:34:56-06:00", NULL); + /* Start with this, per iCalendar's convention for a single-day all-day event: + * + * start = midnight + * end = next midnight + * all_day = true + * + * Then, pass that on to the widgets, and check that they display the same date for start/end, + * since users see "all day, start=2025/03/03, end=2025/03/03" and think sure, a single all-day. + * + * Then, change the end date to one day later, to get a two-day event. + * + * Check that the values gets back end = (start + 2 days) + + * FIXME: here, do the above + */ + + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); g_assert (date != NULL); - g_autoptr (GDateTime) one_hour_later = g_date_time_new_from_iso8601 ("20250303T13:34:56-06:00", NULL); - g_assert (one_hour_later != NULL); + g_autoptr (GDateTime) next_day = g_date_time_new_from_iso8601 ("20250304T00:00:00-06:00", NULL); + g_assert (next_day != NULL); g_autoptr (GcalScheduleValues) values = g_new0 (GcalScheduleValues, 1); *values = (GcalScheduleValues) { - .all_day = FALSE, + .all_day = TRUE, .orig_date_start = g_date_time_ref (date), - .orig_date_end = g_date_time_ref (one_hour_later), + .orig_date_end = g_date_time_ref (next_day), .date_start = g_date_time_ref (date), - .date_end = g_date_time_ref (one_hour_later), + .date_end = g_date_time_ref (next_day), .recur = NULL, .time_format = GCAL_TIME_FORMAT_24H, }; - g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_all_day (values, TRUE); + /* Compute the widget state from the values; check the displayed dates (should be the same) */ - g_autoptr (WidgetState) state = widget_state_from_values (new_values); + g_autoptr (WidgetState) state = widget_state_from_values (values); g_assert_true (state->schedule_type_all_day); g_assert_true (state->date_widgets_visible); @@ -1057,10 +1061,22 @@ test_turning_all_day_on_displays_sensible_dates (void) g_assert_cmpint (g_date_time_get_year (state->date_time_end), ==, 2025); g_assert_cmpint (g_date_time_get_month (state->date_time_end), ==, 3); - /* test failure: - * g_assert_cmpint (g_date_time_get_day_of_month (state->date_time_end), ==, 3); - * this is because we subtract a day from the all_day end_date - */ + g_assert_cmpint (g_date_time_get_day_of_month (state->date_time_end), ==, 3); + + /* Add a day to the end date */ + + g_autoptr (GDateTime) displayed_end_date_plus_one_day = g_date_time_new_from_iso8601 ("20250304T00:00:00-06:00", NULL); + g_assert (displayed_end_date_plus_one_day != NULL); + GcalScheduleValues *new_values = gcal_schedule_values_set_end_date (values, displayed_end_date_plus_one_day); + + /* Check the real iCalendar value for the new date, should be different from the displayed value */ + + g_assert_cmpint (g_date_time_get_day_of_month (new_values->date_end), ==, 5); + + /* Roundtrip back to widgets and check the displayed value */ + + g_autoptr (WidgetState) new_state = widget_state_from_values (new_values); + g_assert_cmpint (g_date_time_get_day_of_month (new_state->date_time_end), ==, 4); } void @@ -1074,6 +1090,6 @@ gcal_schedule_section_add_tests (void) test_setting_start_datetime_preserves_end_timezone); g_test_add_func ("/event_editor/schedule_section/setting_end_datetime_preserves_start_timezone", test_setting_end_datetime_preserves_start_timezone); - g_test_add_func ("/event_editor/schedule_section/turning_all_day_on_displays_sensible_dates", - test_turning_all_day_on_displays_sensible_dates); + g_test_add_func ("/event_editor/schedule_section/all_day_displays_sensible_dates_and_roundtrips", + test_all_day_displays_sensible_dates_and_roundtrips); } -- GitLab From 0dfd19ebd1a389dc633cdbc4cf015bc934ce4695 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Wed, 5 Mar 2025 21:59:06 -0600 Subject: [PATCH 41/63] Remove obsolete comment Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index f6e23d309..a601ba9d7 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -1025,8 +1025,6 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) * Then, change the end date to one day later, to get a two-day event. * * Check that the values gets back end = (start + 2 days) - - * FIXME: here, do the above */ g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); -- GitLab From 5172b9438a84229743a698200c1c14c893b5c1a6 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 6 Mar 2025 17:42:12 -0600 Subject: [PATCH 42/63] Add a real test for #43 I had broken the engine; now it should be fixed. What is broken now is ::changed(), but I only see it in the GUI. I'll try to add a test for that next. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 116 +++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index a601ba9d7..a2d800fd1 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -134,6 +134,79 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all { GcalScheduleValues *copy = gcal_schedule_values_copy (values); + if (all_day == values->all_day) + { + return copy; + } + + if (all_day) + { + /* We are switching from a time-slot event to an all-day one. If we had + * + * date_start = $day1 + $time + * date_end = $day2 + $time + * + * we want to switch to + * + * date_start = $day1 + 00:00 (midnight) + * date_end = $day2 + 1 day + 00:00 (midnight of the next day) + */ + + GDateTime *start = values->date_start; + GDateTime *end = values->date_end; + + g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), + g_date_time_get_year (start), + g_date_time_get_month (start), + g_date_time_get_day_of_month (start), + 0, + 0, + 0.0); + + g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), + g_date_time_get_year (end), + g_date_time_get_month (end), + g_date_time_get_day_of_month (end), + 0, + 0, + 0.0); + g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, 1); + + gcal_set_date_time (©->date_start, new_start); + gcal_set_date_time (©->date_end, new_end); + } + else + { + /* We are switching from an all-day event to a time-slot one. In this case, + * we want to preserve the current dates, but restore the times of the original + * event. + */ + + GDateTime *start = values->date_start; + GDateTime *end = values->date_end; + + g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), + g_date_time_get_year (start), + g_date_time_get_month (start), + g_date_time_get_day_of_month (start), + g_date_time_get_hour (values->orig_date_start), + g_date_time_get_minute (values->orig_date_start), + g_date_time_get_seconds (values->orig_date_start)); + + g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), + g_date_time_get_year (end), + g_date_time_get_month (end), + g_date_time_get_day_of_month (end), + g_date_time_get_hour (values->orig_date_end), + g_date_time_get_minute (values->orig_date_end), + g_date_time_get_seconds (values->orig_date_end)); + + g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, -1); + + gcal_set_date_time (©->date_start, new_start); + gcal_set_date_time (©->date_end, new_end); + } + copy->all_day = all_day; return copy; } @@ -557,6 +630,7 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, GCAL_ENTRY; + gcal_event_set_all_day (event, self->values->all_day); gcal_event_set_date_start (event, self->values->date_start); gcal_event_set_date_end (event, self->values->date_end); @@ -1077,6 +1151,46 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) g_assert_cmpint (g_date_time_get_day_of_month (new_state->date_time_end), ==, 4); } +static void +test_43_switching_to_all_day_preserves_timezones (void) +{ + g_test_bug ("43"); + /* https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/43 + * + * Given an event that is not all-day, e.g. from $day1-12:00 to $day2-13:00, if one turns it to an + * all-day event, we want to switch the dates like this: + * + * start: $day1's 00:00 + * end: $day2's 24:00 (e.g. ($day2 + 1)'s 00:00) + * + * Also test that after that, changing the dates and turning off all-day restores the + * original times. + */ + + g_autoptr (GcalScheduleValues) values = values_with_date_times ("20250303T12:00:00-06:00", + "20250304T13:00:00-06:00"); + + /* set all-day and check that this modified the times to midnight */ + + g_autoptr (GcalScheduleValues) all_day_values = gcal_schedule_values_set_all_day (values, TRUE); + + g_autoptr (GDateTime) expected_start_date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); + g_autoptr (GDateTime) expected_end_date = g_date_time_new_from_iso8601 ("20250305T00:00:00-06:00", NULL); + + g_assert (g_date_time_equal (all_day_values->date_start, expected_start_date)); + g_assert (g_date_time_equal (all_day_values->date_end, expected_end_date)); + + /* turn off all-day; check that times were restored */ + + g_autoptr (GcalScheduleValues) not_all_day_values = gcal_schedule_values_set_all_day (all_day_values, FALSE); + + g_autoptr (GDateTime) expected_start_date2 = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); + g_autoptr (GDateTime) expected_end_date2 = g_date_time_new_from_iso8601 ("20250304T13:00:00-06:00", NULL); + + g_assert (g_date_time_equal (not_all_day_values->date_start, expected_start_date2)); + g_assert (g_date_time_equal (not_all_day_values->date_end, expected_end_date2)); +} + void gcal_schedule_section_add_tests (void) { @@ -1090,4 +1204,6 @@ gcal_schedule_section_add_tests (void) test_setting_end_datetime_preserves_start_timezone); g_test_add_func ("/event_editor/schedule_section/all_day_displays_sensible_dates_and_roundtrips", test_all_day_displays_sensible_dates_and_roundtrips); + g_test_add_func ("/event_editor/schedule_section/43_switching_to_all_day_preserves_timezones", + test_43_switching_to_all_day_preserves_timezones); } -- GitLab From 9d8c6486eb48d37519875a9793c53ede755ea21a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 7 Mar 2025 10:57:49 -0600 Subject: [PATCH 43/63] gcal_schedule_section_day_changed() - Use the original dates The original code from commit 10234653 is GCAL_RETURN (gcal_date_time_compare_date (start_date, gcal_event_get_date_start (self->event)) < 0 || gcal_date_time_compare_date (end_date, gcal_event_get_date_end (self->event)) > 0); but it doesn't explain why it just checks for the modified event getting "wider" (earlier start, later end) than the original event. I don't understand that logic, i.e. why doesn't it just check that the dates changed. Still, now that we keep the original date-time in the GcalScheduleValues, use them to preserve the original semantics. I'm not sure exactly what to test about this... a test to preserve those semantics? A test that the function, per its name, returns true if the days are strictly different? Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index a2d800fd1..8e752b9ff 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -888,28 +888,12 @@ gcal_schedule_section_recurrence_changed (GcalScheduleSection *self) gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self) { - GDateTime *start_date, *end_date; - gboolean all_day; - g_return_val_if_fail (GCAL_IS_SCHEDULE_SECTION (self), FALSE); GCAL_ENTRY; - all_day = all_day_selected (self); - - if (all_day) - { - start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - } - else - { - start_date = gcal_date_chooser_row_get_date (self->start_date_row); - end_date = gcal_date_chooser_row_get_date (self->end_date_row); - } - - GCAL_RETURN (gcal_date_time_compare_date (start_date, self->values->date_start) < 0 || - gcal_date_time_compare_date (end_date, self->values->date_end) > 0); + GCAL_RETURN (gcal_date_time_compare_date (self->values->date_start, self->values->orig_date_start) < 0 || + gcal_date_time_compare_date (self->values->date_end, self->values->orig_date_end) > 0); } /** -- GitLab From 02a596e967b13ab7c85fe2608160a9ec119ee5e9 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 10 Mar 2025 17:34:16 -0600 Subject: [PATCH 44/63] GcalScheduleValues: store the original value of the all_day flag We'll need this for ::changed() Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 4 +++- src/gui/event-editor/gcal-schedule-section.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 8e752b9ff..7a9477bfd 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -119,6 +119,7 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) GcalScheduleValues *copy = g_new0 (GcalScheduleValues, 1); copy->all_day = values->all_day; + copy->orig_all_day = values->orig_all_day; copy->orig_date_start = values->orig_date_start ? g_date_time_ref (values->orig_date_start) : NULL; copy->orig_date_end = values->orig_date_end ? g_date_time_ref (values->orig_date_end) : NULL; copy->date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; @@ -912,7 +913,8 @@ gcal_schedule_values_from_event (GcalEvent *event, { GcalRecurrence *recur = gcal_event_get_recurrence (event); - values->all_day = gcal_event_get_all_day (event); + values->orig_all_day = gcal_event_get_all_day (event); + values->all_day = values->orig_all_day; values->orig_date_start = g_date_time_ref (gcal_event_get_date_start (event)); values->date_start = g_date_time_ref (values->orig_date_start); diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index fda4cedd7..bfae481a3 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -44,6 +44,7 @@ typedef struct /* Original times from the GcalEvent. We keep these around to be able to reconstruct * the event's duration in case the all-day toggle gets turned on and off repeatedly. */ + gboolean orig_all_day; GDateTime *orig_date_start; GDateTime *orig_date_end; -- GitLab From dfc3833253dd9b48824ae22ed5e6353aeb709f5d Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 10 Mar 2025 17:35:31 -0600 Subject: [PATCH 45/63] gcal_schedule_values_free(): Oops, also free the orig_ fields Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 7a9477bfd..e48345676 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -942,6 +942,8 @@ gcal_schedule_values_free (GcalScheduleValues *values) { values->all_day = FALSE; + g_clear_pointer (&values->orig_date_start, g_date_time_unref); + g_clear_pointer (&values->orig_date_end, g_date_time_unref); g_clear_pointer (&values->date_start, g_date_time_unref); g_clear_pointer (&values->date_end, g_date_time_unref); g_clear_pointer (&values->recur, gcal_recurrence_unref); -- GitLab From 324e3ef71c7d107459a9c47bcdac02f71566212a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Mon, 10 Mar 2025 21:34:07 -0600 Subject: [PATCH 46/63] Move recurrence fields into GcalScheduleSectionValues, split it into "orig" and "curr" values This commit does a bit too much, but with these themes: - Move the recurrence fields into GcalScheduleValues. This was needed for consistency, anyway, and... - ... the next step was to make ::changed() work, which actually required knowing the original and current values of the recurrence as well as the dates. - But then, having a field "orig_foo" for each field "foo" starts getting unwieldy. So, I moved things around: - GcalScheduleValues has start/end dates and all_day, and the recurrence. - GcalScheduleSectionValues is new; it has the "orig" and "curr" values of the previous type. This simplifies memory management since each field can be freed with gcal_schedule_values_free() instead of duplicating _free() for each sub-field. - GcalScheduleSectionValues also has time_format, as a way to decouple that value from GcalContext. It's not in orig/curr because it does not pertain the event; it's just a display-related thing. - Actually fix gcal_schedule_section_changed(), gcal_schedule_section_recurrence_changed(), and gcal_schedule_section_day_changed(). These become simple since they just look at the orig/curr fields, without having to touch widgetry. - All test pass. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 636 ++++++++++--------- src/gui/event-editor/gcal-schedule-section.h | 28 +- 2 files changed, 341 insertions(+), 323 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index e48345676..b8c74c67d 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -37,7 +37,7 @@ struct _GcalScheduleSection { GtkBox parent; - GcalScheduleValues *values; + GcalScheduleSectionValues *values; AdwToggleGroup *schedule_type_toggle_group; AdwPreferencesGroup *start_date_group; @@ -68,6 +68,7 @@ typedef struct gboolean schedule_type_all_day; gboolean date_widgets_visible; gboolean date_time_widgets_visible; + GcalTimeFormat time_format; /* Note that the displayed date/time may not correspond to the real data in the @@ -75,6 +76,14 @@ typedef struct */ GDateTime *date_time_start; GDateTime *date_time_end; + + struct { + gboolean duration_combo_visible; + GcalRecurrenceFrequency frequency; + GcalRecurrenceLimitType limit_type; + guint limit_count; + GDateTime *limit_until; + } recurrence; } WidgetState; static void widget_state_free (WidgetState *state); @@ -113,29 +122,60 @@ static void on_schedule_type_changed_cb (GtkWidget GParamSpec *pspec, GcalScheduleSection *self); -static GcalScheduleValues * +static GcalScheduleValues gcal_schedule_values_copy (const GcalScheduleValues *values) { - GcalScheduleValues *copy = g_new0 (GcalScheduleValues, 1); - - copy->all_day = values->all_day; - copy->orig_all_day = values->orig_all_day; - copy->orig_date_start = values->orig_date_start ? g_date_time_ref (values->orig_date_start) : NULL; - copy->orig_date_end = values->orig_date_end ? g_date_time_ref (values->orig_date_end) : NULL; - copy->date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; - copy->date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; - copy->recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; + GcalScheduleValues copy; + + copy.all_day = values->all_day; + copy.date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; + copy.date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; + copy.recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; + + return copy; +} + +static GcalScheduleSectionValues * +gcal_schedule_section_values_copy (const GcalScheduleSectionValues *values) +{ + GcalScheduleSectionValues *copy = g_new0 (GcalScheduleSectionValues, 1); + + copy->orig = gcal_schedule_values_copy (&values->orig); + copy->curr = gcal_schedule_values_copy (&values->curr); copy->time_format = values->time_format; return copy; } -static GcalScheduleValues * -gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all_day) +static void +gcal_schedule_values_free (GcalScheduleValues *values) +{ + values->all_day = FALSE; + + g_clear_pointer (&values->date_start, g_date_time_unref); + g_clear_pointer (&values->date_end, g_date_time_unref); + g_clear_pointer (&values->recur, gcal_recurrence_unref); +} + +/** + * gcal_schedule_section_values_free(): + * + * Frees the contents of @values. + */ +void +gcal_schedule_section_values_free (GcalScheduleSectionValues *values) +{ + gcal_schedule_values_free (&values->orig); + gcal_schedule_values_free (&values->curr); + g_free (values); +} + +static GcalScheduleSectionValues* +gcal_schedule_section_values_set_all_day (const GcalScheduleSectionValues *values, gboolean all_day) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); - if (all_day == values->all_day) + if (all_day == values->curr.all_day) { return copy; } @@ -153,8 +193,8 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all * date_end = $day2 + 1 day + 00:00 (midnight of the next day) */ - GDateTime *start = values->date_start; - GDateTime *end = values->date_end; + GDateTime *start = values->curr.date_start; + GDateTime *end = values->curr.date_end; g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), g_date_time_get_year (start), @@ -173,8 +213,8 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all 0.0); g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, 1); - gcal_set_date_time (©->date_start, new_start); - gcal_set_date_time (©->date_end, new_end); + gcal_set_date_time (©->curr.date_start, new_start); + gcal_set_date_time (©->curr.date_end, new_end); } else { @@ -183,39 +223,39 @@ gcal_schedule_values_set_all_day (const GcalScheduleValues *values, gboolean all * event. */ - GDateTime *start = values->date_start; - GDateTime *end = values->date_end; + GDateTime *start = values->curr.date_start; + GDateTime *end = values->curr.date_end; g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), g_date_time_get_year (start), g_date_time_get_month (start), g_date_time_get_day_of_month (start), - g_date_time_get_hour (values->orig_date_start), - g_date_time_get_minute (values->orig_date_start), - g_date_time_get_seconds (values->orig_date_start)); + g_date_time_get_hour (values->orig.date_start), + g_date_time_get_minute (values->orig.date_start), + g_date_time_get_seconds (values->orig.date_start)); g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), g_date_time_get_year (end), g_date_time_get_month (end), g_date_time_get_day_of_month (end), - g_date_time_get_hour (values->orig_date_end), - g_date_time_get_minute (values->orig_date_end), - g_date_time_get_seconds (values->orig_date_end)); + g_date_time_get_hour (values->orig.date_end), + g_date_time_get_minute (values->orig.date_end), + g_date_time_get_seconds (values->orig.date_end)); g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, -1); - gcal_set_date_time (©->date_start, new_start); - gcal_set_date_time (©->date_end, new_end); + gcal_set_date_time (©->curr.date_start, new_start); + gcal_set_date_time (©->curr.date_end, new_end); } - copy->all_day = all_day; + copy->curr.all_day = all_day; return copy; } -static GcalScheduleValues * -gcal_schedule_values_set_time_format (const GcalScheduleValues *values, GcalTimeFormat time_format) +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_time_format (const GcalScheduleSectionValues *values, GcalTimeFormat time_format) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); copy->time_format = time_format; return copy; @@ -224,16 +264,16 @@ gcal_schedule_values_set_time_format (const GcalScheduleValues *values, GcalTime /* The start_date_row widget has changed. We need to sync it to the values and * adjust the other values based on it. */ -static GcalScheduleValues * -gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime *start) +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_start_date (const GcalScheduleSectionValues *values, GDateTime *start) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); - gcal_set_date_time (©->date_start, start); + gcal_set_date_time (©->curr.date_start, start); - if (g_date_time_compare (start, copy->date_end) == 1) + if (g_date_time_compare (start, copy->curr.date_end) == 1) { - gcal_set_date_time (©->date_end, start); + gcal_set_date_time (©->curr.date_end, start); } return copy; @@ -242,85 +282,85 @@ gcal_schedule_values_set_start_date (const GcalScheduleValues *values, GDateTime /* The end_date_row widget has changed. We need to sync it to the values and * adjust the other values based on it. */ -static GcalScheduleValues * -gcal_schedule_values_set_end_date (const GcalScheduleValues *values, GDateTime *end) +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_end_date (const GcalScheduleSectionValues *values, GDateTime *end) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); - if (copy->all_day) + if (copy->curr.all_day) { /* See the comment "While in iCalendar..." in widget_state_from_values(). Here we * take the human-readable end-date, and turn it into the appropriate value for * iCalendar. */ g_autoptr (GDateTime) fake_end_date = g_date_time_add_days (end, 1); - gcal_set_date_time (©->date_end, fake_end_date); + gcal_set_date_time (©->curr.date_end, fake_end_date); } else { - gcal_set_date_time (©->date_end, end); + gcal_set_date_time (©->curr.date_end, end); } - if (g_date_time_compare (copy->date_start, end) == 1) + if (g_date_time_compare (copy->curr.date_start, end) == 1) { - gcal_set_date_time (©->date_start, end); + gcal_set_date_time (©->curr.date_start, end); } return copy; } -static GcalScheduleValues * -gcal_schedule_values_set_start_date_time (const GcalScheduleValues *values, GDateTime *start) +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_start_date_time (const GcalScheduleSectionValues *values, GDateTime *start) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); - gcal_set_date_time (©->date_start, start); + gcal_set_date_time (©->curr.date_start, start); - if (g_date_time_compare (start, copy->date_end) == 1) + if (g_date_time_compare (start, copy->curr.date_end) == 1) { - GTimeZone *end_tz = g_date_time_get_timezone (copy->date_end); + GTimeZone *end_tz = g_date_time_get_timezone (copy->curr.date_end); g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); - gcal_set_date_time (©->date_end, new_end_in_end_tz); + gcal_set_date_time (©->curr.date_end, new_end_in_end_tz); } return copy; } -static GcalScheduleValues * -gcal_schedule_values_set_end_date_time (const GcalScheduleValues *values, GDateTime *end) +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_end_date_time (const GcalScheduleSectionValues *values, GDateTime *end) { - GcalScheduleValues *copy = gcal_schedule_values_copy (values); + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); - gcal_set_date_time (©->date_end, end); + gcal_set_date_time (©->curr.date_end, end); - if (g_date_time_compare (copy->date_start, end) == 1) + if (g_date_time_compare (copy->curr.date_start, end) == 1) { - GTimeZone *start_tz = g_date_time_get_timezone (copy->date_start); + GTimeZone *start_tz = g_date_time_get_timezone (copy->curr.date_start); g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); - gcal_set_date_time (©->date_start, new_start_in_start_tz); + gcal_set_date_time (©->curr.date_start, new_start_in_start_tz); } return copy; } static WidgetState * -widget_state_from_values (const GcalScheduleValues *values) +widget_state_from_values (const GcalScheduleSectionValues *values) { WidgetState *state = g_new0 (WidgetState, 1); - state->schedule_type_all_day = values->all_day; - state->date_widgets_visible = values->all_day; - state->date_time_widgets_visible = !values->all_day; + state->schedule_type_all_day = values->curr.all_day; + state->date_widgets_visible = values->curr.all_day; + state->date_time_widgets_visible = !values->curr.all_day; state->time_format = values->time_format; - g_assert (values->date_start); - state->date_time_start = g_date_time_ref (values->date_start); + g_assert (values->curr.date_start); + state->date_time_start = g_date_time_ref (values->curr.date_start); - if (values->all_day) + if (values->curr.all_day) { /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't * want to show the user a single-day all-day event as starting on Feb 1 and ending @@ -334,12 +374,45 @@ widget_state_from_values (const GcalScheduleValues *values) * * Keep in sync with gcal_schedule_values_set_end_date(). */ - state->date_time_end = g_date_time_add_days (values->date_end, -1); + state->date_time_end = g_date_time_add_days (values->curr.date_end, -1); + } + else + { + g_assert (values->curr.date_end); + state->date_time_end = g_date_time_ref (values->curr.date_end); + } + + if (values->curr.recur && values->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT) + { + state->recurrence.duration_combo_visible = TRUE; + state->recurrence.frequency = values->curr.recur->frequency; + state->recurrence.limit_type = values->curr.recur->limit_type; + + switch (values->curr.recur->limit_type) + { + case GCAL_RECURRENCE_COUNT: + state->recurrence.limit_count = values->curr.recur->limit.count; + break; + + case GCAL_RECURRENCE_UNTIL: + state->recurrence.limit_until = g_date_time_ref (values->curr.recur->limit.until); + break; + + case GCAL_RECURRENCE_FOREVER: + state->recurrence.limit_count = 0; + break; + + default: + g_assert_not_reached (); + } } else { - g_assert (values->date_end); - state->date_time_end = g_date_time_ref (values->date_end); + state->recurrence.duration_combo_visible = FALSE; + state->recurrence.frequency = GCAL_RECURRENCE_NO_REPEAT; + state->recurrence.limit_type = GCAL_RECURRENCE_FOREVER; + state->recurrence.limit_count = 0; + state->recurrence.limit_until = NULL; } return state; @@ -350,6 +423,12 @@ widget_state_free (WidgetState *state) { g_date_time_unref (state->date_time_start); g_date_time_unref (state->date_time_end); + + if (state->recurrence.limit_until) + { + g_date_time_unref (state->recurrence.limit_until); + } + g_free (state); } @@ -423,24 +502,39 @@ update_widgets (GcalScheduleSection *self, gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, state->date_time_start); gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, state->date_time_end); + /* Recurrences */ + gtk_widget_set_visible (self->repeat_duration_combo, state->recurrence.duration_combo_visible); + + adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_combo), state->recurrence.frequency); + adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), state->recurrence.limit_type); + + gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), state->recurrence.limit_count); + gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), state->recurrence.limit_until); + unblock_date_signals (self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } +/* Recomputes and sets the widget state. Assumes self->values has already been updated. */ +static void +refresh (GcalScheduleSection *self) +{ + g_autoptr (WidgetState) state = widget_state_from_values (self->values); + update_widgets (self, state); +} + /* Updates the widgets, and the current values, from the specified ones. * * This will free the current values and replace them with new ones. */ static void -update_from_values (GcalScheduleSection *self, - GcalScheduleValues *values) +update_from_section_values (GcalScheduleSection *self, + GcalScheduleSectionValues *values) { - g_autoptr (WidgetState) state = widget_state_from_values (values); - - update_widgets (self, state); - - gcal_schedule_values_free (self->values); + gcal_schedule_section_values_free (self->values); self->values = values; + + refresh (self); } /* @@ -453,8 +547,8 @@ on_schedule_type_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { gboolean all_day = all_day_selected (self); - GcalScheduleValues *updated = gcal_schedule_values_set_all_day (self->values, all_day); - update_from_values (self, updated); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_all_day (self->values, all_day); + update_from_section_values (self, updated); } static void @@ -463,8 +557,8 @@ on_start_date_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *start = gcal_date_chooser_row_get_date (self->start_date_row); - GcalScheduleValues *updated = gcal_schedule_values_set_start_date (self->values, start); - update_from_values (self, updated); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_start_date (self->values, start); + update_from_section_values (self, updated); } static void @@ -473,8 +567,8 @@ on_end_date_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *end = gcal_date_chooser_row_get_date (self->end_date_row); - GcalScheduleValues *updated = gcal_schedule_values_set_end_date (self->values, end); - update_from_values (self, updated); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_end_date (self->values, end); + update_from_section_values (self, updated); } static void @@ -483,8 +577,8 @@ on_start_date_time_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - GcalScheduleValues *updated = gcal_schedule_values_set_start_date_time (self->values, start); - update_from_values (self, updated); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_start_date_time (self->values, start); + update_from_section_values (self, updated); } static void @@ -493,8 +587,8 @@ on_end_date_time_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - GcalScheduleValues *updated = gcal_schedule_values_set_end_date_time (self->values, end); - update_from_values (self, updated); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_end_date_time (self->values, end); + update_from_section_values (self, updated); } static void @@ -528,11 +622,9 @@ on_time_format_changed_cb (GcalScheduleSection *self) { GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - GcalScheduleValues *updated = gcal_schedule_values_set_time_format (self->values, time_format); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_time_format (self->values, time_format); - WidgetState *state = widget_state_from_values (updated); - update_widgets (self, state); - widget_state_free (state); + update_from_section_values (self, updated); } @@ -557,11 +649,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, GcalEventEditorFlags flags) { GcalScheduleSection *self; - GcalRecurrenceLimitType limit_type; - GcalRecurrenceFrequency frequency; GcalTimeFormat time_format; - GcalScheduleValues *values; - g_autoptr (WidgetState) state = NULL; GCAL_ENTRY; @@ -571,54 +659,12 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, self->flags = flags; time_format = gcal_context_get_time_format (self->context); - self->values = gcal_schedule_values_from_event (event, time_format); - values = self->values; /* alias to avoid "self->values" everywhere */ + self->values = gcal_schedule_section_values_from_event (event, time_format); if (!event) GCAL_RETURN (); - state = widget_state_from_values (values); - update_widgets (self, state); - - /* Recurrences */ - if (values->recur) - { - frequency = values->recur->frequency; - limit_type = values->recur->limit_type; - } - else - { - frequency = GCAL_RECURRENCE_NO_REPEAT; - limit_type = GCAL_RECURRENCE_FOREVER; - } - - adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_combo), frequency); - adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), limit_type); - - if (frequency == GCAL_RECURRENCE_NO_REPEAT) - { - gtk_widget_set_visible (self->repeat_duration_combo, FALSE); - } - else - { - gtk_widget_set_visible (self->repeat_duration_combo, TRUE); - gtk_widget_set_visible (self->repeat_duration_combo, TRUE); - } - - switch (limit_type) - { - case GCAL_RECURRENCE_COUNT: - gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), values->recur->limit.count); - break; - - case GCAL_RECURRENCE_UNTIL: - gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), values->recur->limit.until); - break; - - case GCAL_RECURRENCE_FOREVER: - gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), 0); - break; - } + refresh (self); GCAL_EXIT; } @@ -631,9 +677,9 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, GCAL_ENTRY; - gcal_event_set_all_day (event, self->values->all_day); - gcal_event_set_date_start (event, self->values->date_start); - gcal_event_set_date_end (event, self->values->date_end); + gcal_event_set_all_day (event, self->values->curr.all_day); + gcal_event_set_date_start (event, self->values->curr.date_start); + gcal_event_set_date_end (event, self->values->curr.date_end); /* Check Repeat popover and set recurrence-rules accordingly */ @@ -653,7 +699,7 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, recur->limit.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); /* Only apply the new recurrence if it's different from the old one */ - if (!gcal_recurrence_is_equal (self->values->recur, recur)) + if (!gcal_recurrence_is_equal (self->values->curr.recur, recur)) { /* Remove the previous recurrence... */ remove_recurrence_properties (event); @@ -680,71 +726,65 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) gcal_schedule_section_apply_to_event (self, self->event); - gcal_schedule_values_free (self->values); + gcal_schedule_section_values_free (self->values); GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - self->values = gcal_schedule_values_from_event (self->event, time_format); + self->values = gcal_schedule_section_values_from_event (self->event, time_format); GCAL_EXIT; } static gboolean -gcal_schedule_section_changed (GcalEventEditorSection *section) +recurrence_changed (const GcalScheduleSectionValues *values) { - GcalScheduleSection *self; - GDateTime *start_date, *end_date, *prev_start_date, *prev_end_date; - GTimeZone *start_tz, *end_tz, *prev_start_tz, *prev_end_tz; - gboolean was_all_day; - gboolean all_day; + return !gcal_recurrence_is_equal (values->orig.recur, values->curr.recur); +} - GCAL_ENTRY; +static gboolean +day_changed (const GcalScheduleSectionValues *values) +{ + return (gcal_date_time_compare_date (values->curr.date_start, values->orig.date_start) < 0 || + gcal_date_time_compare_date (values->curr.date_end, values->orig.date_end) > 0); +} - self = GCAL_SCHEDULE_SECTION (section); - all_day = all_day_selected (self); - was_all_day = self->values->all_day; - - /* All day */ - if (all_day != was_all_day) - GCAL_RETURN (TRUE); - - if (!all_day) - { - start_date = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - end_date = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - } - else - { - start_date = gcal_date_chooser_row_get_date (self->start_date_row); - end_date = gcal_date_chooser_row_get_date (self->end_date_row); - } - prev_start_date = self->values->date_start; - prev_end_date = self->values->date_end; - - start_tz = g_date_time_get_timezone (start_date); - end_tz = g_date_time_get_timezone (end_date); - prev_start_tz = g_date_time_get_timezone (prev_start_date); - prev_end_tz = g_date_time_get_timezone (prev_end_date); - - /* Start date */ - if (!g_date_time_equal (start_date, prev_start_date)) - GCAL_RETURN (TRUE); - if (g_strcmp0 (g_time_zone_get_identifier (start_tz), g_time_zone_get_identifier (prev_start_tz)) != 0) - GCAL_RETURN (TRUE); - - /* End date */ - if (all_day) - { - GDateTime *fake_end_date = g_date_time_add_days (end_date, 1); - end_date = fake_end_date; - } +static gboolean +values_changed (const GcalScheduleSectionValues *values) +{ + const GcalScheduleValues *orig = &values->orig; + const GcalScheduleValues *curr = &values->curr; + + if (orig->all_day != curr->all_day) + return TRUE; + + GTimeZone *orig_start_tz, *orig_end_tz, *start_tz, *end_tz; + orig_start_tz = g_date_time_get_timezone (orig->date_start); + orig_end_tz = g_date_time_get_timezone (orig->date_end); + start_tz = g_date_time_get_timezone (curr->date_start); + end_tz = g_date_time_get_timezone (curr->date_end); + + if (!g_date_time_equal (orig->date_start, curr->date_start)) + return TRUE; - if (!g_date_time_equal (end_date, self->values->date_end)) - GCAL_RETURN (TRUE); - if (g_strcmp0 (g_time_zone_get_identifier (end_tz), g_time_zone_get_identifier (prev_end_tz)) != 0) - GCAL_RETURN (TRUE); + if (g_strcmp0 (g_time_zone_get_identifier (start_tz), g_time_zone_get_identifier (orig_start_tz)) != 0) + return TRUE; - /* Recurrency */ - GCAL_RETURN (gcal_schedule_section_recurrence_changed (self)); + if (!g_date_time_equal (orig->date_end, curr->date_end)) + return TRUE; + + if (g_strcmp0 (g_time_zone_get_identifier (end_tz), g_time_zone_get_identifier (orig_end_tz)) != 0) + return TRUE; + + return recurrence_changed (values); +} + +static gboolean +gcal_schedule_section_changed (GcalEventEditorSection *section) +{ + GcalScheduleSection *self = GCAL_SCHEDULE_SECTION (section); + + GCAL_ENTRY; + + GCAL_RETURN (values_changed (self->values)); } static void @@ -765,7 +805,7 @@ gcal_schedule_section_finalize (GObject *object) { GcalScheduleSection *self = (GcalScheduleSection *)object; - g_clear_pointer (&self->values, gcal_schedule_values_free); + g_clear_pointer (&self->values, gcal_schedule_section_values_free); g_clear_object (&self->context); g_clear_object (&self->event); @@ -866,24 +906,9 @@ gcal_schedule_section_init (GcalScheduleSection *self) gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self) { - g_autoptr (GcalRecurrence) recurrence = NULL; - GcalRecurrenceFrequency freq; - g_return_val_if_fail (GCAL_IS_SCHEDULE_SECTION (self), FALSE); - freq = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); - if (freq == GCAL_RECURRENCE_NO_REPEAT && !self->values->recur) - GCAL_RETURN (FALSE); - - recurrence = gcal_recurrence_new (); - recurrence->frequency = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); - recurrence->limit_type = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_duration_combo)); - if (recurrence->limit_type == GCAL_RECURRENCE_UNTIL) - recurrence->limit.until = g_date_time_ref (gcal_date_selector_get_date (GCAL_DATE_SELECTOR (self->until_date_selector))); - else if (recurrence->limit_type == GCAL_RECURRENCE_COUNT) - recurrence->limit.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); - - GCAL_RETURN (!gcal_recurrence_is_equal (recurrence, self->values->recur)); + return recurrence_changed (self->values); } gboolean @@ -891,69 +916,56 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) { g_return_val_if_fail (GCAL_IS_SCHEDULE_SECTION (self), FALSE); - GCAL_ENTRY; - - GCAL_RETURN (gcal_date_time_compare_date (self->values->date_start, self->values->orig_date_start) < 0 || - gcal_date_time_compare_date (self->values->date_end, self->values->orig_date_end) > 0); + return day_changed (self->values); } /** * Extracts the values from @event that are needed to populate #GcalScheduleSection. * - * Returns: a #GcalscheduleValues ready for use. Free it with - * gcal_schedule_values_free(). + * Returns: a #GcalScheduleSectionValues ready for use. Free it with + * gcal_schedule_section_values_free(). */ -GcalScheduleValues * -gcal_schedule_values_from_event (GcalEvent *event, - GcalTimeFormat time_format) +GcalScheduleSectionValues * +gcal_schedule_section_values_from_event (GcalEvent *event, + GcalTimeFormat time_format) { - GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); + GcalScheduleValues values; + memset (&values, 0, sizeof (values)); if (event) { GcalRecurrence *recur = gcal_event_get_recurrence (event); - values->orig_all_day = gcal_event_get_all_day (event); - values->all_day = values->orig_all_day; - - values->orig_date_start = g_date_time_ref (gcal_event_get_date_start (event)); - values->date_start = g_date_time_ref (values->orig_date_start); - - values->orig_date_end = g_date_time_ref (gcal_event_get_date_end (event)); - values->date_end = g_date_time_ref (values->orig_date_end); + values.all_day = gcal_event_get_all_day (event); + values.date_start = g_date_time_ref (gcal_event_get_date_start (event)); + values.date_end = g_date_time_ref (gcal_event_get_date_end (event)); if (recur) { - values->recur = gcal_recurrence_ref (recur); + values.recur = gcal_recurrence_ref (recur); + } + else + { + values.recur = NULL; } } - values->time_format = time_format; + GcalScheduleSectionValues *section_values = g_new0 (GcalScheduleSectionValues, 1); - return values; -} - -/** - * Frees the contents of @values. Does not free the @values pointer itself; - * this structure is meant to be allocated on the stack. - */ -void -gcal_schedule_values_free (GcalScheduleValues *values) -{ - values->all_day = FALSE; - - g_clear_pointer (&values->orig_date_start, g_date_time_unref); - g_clear_pointer (&values->orig_date_end, g_date_time_unref); - g_clear_pointer (&values->date_start, g_date_time_unref); - g_clear_pointer (&values->date_end, g_date_time_unref); - g_clear_pointer (&values->recur, gcal_recurrence_unref); + *section_values = (GcalScheduleSectionValues) { + .orig = gcal_schedule_values_copy (&values), + .curr = values, + .time_format = time_format, + }; - g_free (values); + return section_values; } -/* Builds a new Gcalschedulevalues from two ISO 8601 date-times; be sure to include your timezone if you need it */ -static GcalScheduleValues * -values_with_date_times(const char *start, const char *end) +/* Builds a new GcalscheduleSectgionValues from two ISO 8601 date-times; be sure to + * include your timezone if you need it. This function is just to be used from tests. + **/ +static GcalScheduleValues +values_with_date_times (const char *start, const char *end, gboolean all_day) { g_autoptr (GDateTime) start_date = g_date_time_new_from_iso8601 (start, NULL); g_assert (start_date != NULL); @@ -963,21 +975,32 @@ values_with_date_times(const char *start, const char *end) g_assert (g_date_time_compare (start_date, end_date) == -1); - GcalScheduleValues *values = g_new0 (GcalScheduleValues, 1); - - *values = (GcalScheduleValues) { - .all_day = FALSE, - .orig_date_start = g_date_time_ref (start_date), - .orig_date_end = g_date_time_ref (end_date), + GcalScheduleValues values = { + .all_day = all_day, .date_start = g_date_time_ref (start_date), .date_end = g_date_time_ref (end_date), .recur = NULL, - .time_format = GCAL_TIME_FORMAT_24H, }; return values; } +static GcalScheduleSectionValues * +section_values_with_date_times (const char *start, const char *end, gboolean all_day) +{ + GcalScheduleValues values = values_with_date_times (start, end, all_day); + + GcalScheduleSectionValues *section_values = g_new0 (GcalScheduleSectionValues, 1); + + *section_values = (GcalScheduleSectionValues) { + .orig = gcal_schedule_values_copy (&values), + .curr = values, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + return section_values; +} + static void test_setting_start_date_after_end_date_resets_end_date (void) { @@ -989,14 +1012,15 @@ test_setting_start_date_after_end_date_resets_end_date (void) * * We want to test that end becomes 12:00 as well. */ - g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00"); + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); - g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_start_date (values, two_hours_later); - g_assert (g_date_time_equal (new_values->date_start, two_hours_later)); - g_assert (g_date_time_equal (new_values->date_end, two_hours_later)); + g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_start_date (values, two_hours_later); + g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_later)); + g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_later)); } static void @@ -1010,14 +1034,15 @@ test_setting_end_date_before_start_date_resets_start_date (void) * * We want to test that start becomes 09:00 as well. */ - g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00"); + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); - g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_end_date (values, two_hours_earlier); - g_assert (g_date_time_equal (new_values->date_start, two_hours_earlier)); - g_assert (g_date_time_equal (new_values->date_end, two_hours_earlier)); + g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date (values, two_hours_earlier); + g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_earlier)); + g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_earlier)); } static void @@ -1033,17 +1058,18 @@ test_setting_start_datetime_preserves_end_timezone (void) * * (imagine driving for one hour while crossing timezones) */ - g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00"); - g_assert (g_date_time_compare (values->date_start, values->date_end) == -1); + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); + g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); g_autoptr (GDateTime) new_start = g_date_time_new_from_iso8601 ("20250303T11:30:00-06:00", NULL); g_autoptr (GDateTime) expected_end = g_date_time_new_from_iso8601 ("20250303T13:30:00-05:00", NULL); - g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_start_date_time (values, new_start); - g_assert (g_date_time_equal (new_values->date_start, new_start)); + g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_start_date_time (values, new_start); + g_assert (g_date_time_equal (new_values->curr.date_start, new_start)); - g_assert (g_date_time_equal (new_values->date_end, expected_end)); + g_assert (g_date_time_equal (new_values->curr.date_end, expected_end)); } static void @@ -1059,17 +1085,18 @@ test_setting_end_datetime_preserves_start_timezone (void) * * (imagine driving for one hour while crossing timezones) */ - g_autoptr (GcalScheduleValues) values = values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00"); - g_assert (g_date_time_compare (values->date_start, values->date_end) == -1); + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); + g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); g_autoptr (GDateTime) new_end = g_date_time_new_from_iso8601 ("20250303T09:30:00-05:00", NULL); g_autoptr (GDateTime) expected_start = g_date_time_new_from_iso8601 ("20250303T07:30:00-06:00", NULL); - g_autoptr (GcalScheduleValues) new_values = gcal_schedule_values_set_end_date_time (values, new_end); - g_assert (g_date_time_equal (new_values->date_end, new_end)); + g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date_time (values, new_end); + g_assert (g_date_time_equal (new_values->curr.date_end, new_end)); - g_assert (g_date_time_equal (new_values->date_start, expected_start)); + g_assert (g_date_time_equal (new_values->curr.date_start, expected_start)); } static void @@ -1089,23 +1116,9 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) * Check that the values gets back end = (start + 2 days) */ - g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); - g_assert (date != NULL); - - g_autoptr (GDateTime) next_day = g_date_time_new_from_iso8601 ("20250304T00:00:00-06:00", NULL); - g_assert (next_day != NULL); - - g_autoptr (GcalScheduleValues) values = g_new0 (GcalScheduleValues, 1); - - *values = (GcalScheduleValues) { - .all_day = TRUE, - .orig_date_start = g_date_time_ref (date), - .orig_date_end = g_date_time_ref (next_day), - .date_start = g_date_time_ref (date), - .date_end = g_date_time_ref (next_day), - .recur = NULL, - .time_format = GCAL_TIME_FORMAT_24H, - }; + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times ("20250303T00:00:00-06:00", + "20250304T00:00:00-06:00", + TRUE); /* Compute the widget state from the values; check the displayed dates (should be the same) */ @@ -1127,11 +1140,11 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) g_autoptr (GDateTime) displayed_end_date_plus_one_day = g_date_time_new_from_iso8601 ("20250304T00:00:00-06:00", NULL); g_assert (displayed_end_date_plus_one_day != NULL); - GcalScheduleValues *new_values = gcal_schedule_values_set_end_date (values, displayed_end_date_plus_one_day); + g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date (values, displayed_end_date_plus_one_day); /* Check the real iCalendar value for the new date, should be different from the displayed value */ - g_assert_cmpint (g_date_time_get_day_of_month (new_values->date_end), ==, 5); + g_assert_cmpint (g_date_time_get_day_of_month (new_values->curr.date_end), ==, 5); /* Roundtrip back to widgets and check the displayed value */ @@ -1155,28 +1168,29 @@ test_43_switching_to_all_day_preserves_timezones (void) * original times. */ - g_autoptr (GcalScheduleValues) values = values_with_date_times ("20250303T12:00:00-06:00", - "20250304T13:00:00-06:00"); + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times ("20250303T12:00:00-06:00", + "20250304T13:00:00-06:00", + FALSE); /* set all-day and check that this modified the times to midnight */ - g_autoptr (GcalScheduleValues) all_day_values = gcal_schedule_values_set_all_day (values, TRUE); + g_autoptr (GcalScheduleSectionValues) all_day_values = gcal_schedule_section_values_set_all_day (values, TRUE); g_autoptr (GDateTime) expected_start_date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); g_autoptr (GDateTime) expected_end_date = g_date_time_new_from_iso8601 ("20250305T00:00:00-06:00", NULL); - g_assert (g_date_time_equal (all_day_values->date_start, expected_start_date)); - g_assert (g_date_time_equal (all_day_values->date_end, expected_end_date)); + g_assert (g_date_time_equal (all_day_values->curr.date_start, expected_start_date)); + g_assert (g_date_time_equal (all_day_values->curr.date_end, expected_end_date)); /* turn off all-day; check that times were restored */ - g_autoptr (GcalScheduleValues) not_all_day_values = gcal_schedule_values_set_all_day (all_day_values, FALSE); + g_autoptr (GcalScheduleSectionValues) not_all_day_values = gcal_schedule_section_values_set_all_day (all_day_values, FALSE); g_autoptr (GDateTime) expected_start_date2 = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); g_autoptr (GDateTime) expected_end_date2 = g_date_time_new_from_iso8601 ("20250304T13:00:00-06:00", NULL); - g_assert (g_date_time_equal (not_all_day_values->date_start, expected_start_date2)); - g_assert (g_date_time_equal (not_all_day_values->date_end, expected_end_date2)); + g_assert (g_date_time_equal (not_all_day_values->curr.date_start, expected_start_date2)); + g_assert (g_date_time_equal (not_all_day_values->curr.date_end, expected_end_date2)); } void diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index bfae481a3..3181a4faf 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -41,29 +41,33 @@ typedef struct { gboolean all_day; - /* Original times from the GcalEvent. We keep these around to be able to reconstruct - * the event's duration in case the all-day toggle gets turned on and off repeatedly. - */ - gboolean orig_all_day; - GDateTime *orig_date_start; - GDateTime *orig_date_end; - GDateTime *date_start; GDateTime *date_end; GcalRecurrence *recur; - GcalTimeFormat time_format; } GcalScheduleValues; +typedef struct +{ + /* original values from event */ + GcalScheduleValues orig; + + /* current values, as modified by actions from widgets */ + GcalScheduleValues curr; + + /* copied from GcalContext to avoid a dependency on it */ + GcalTimeFormat time_format; +} GcalScheduleSectionValues; + gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self); gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self); -GcalScheduleValues *gcal_schedule_values_from_event (GcalEvent *event, - GcalTimeFormat time_format); -void gcal_schedule_values_free (GcalScheduleValues *values); +GcalScheduleSectionValues *gcal_schedule_section_values_from_event (GcalEvent *event, + GcalTimeFormat time_format); +void gcal_schedule_section_values_free (GcalScheduleSectionValues *values); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleValues, gcal_schedule_values_free); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleSectionValues, gcal_schedule_section_values_free); /* Tests */ -- GitLab From d1ccbdf4fbbb2c1590f3c59a86971cbe4e3b756b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 13 Mar 2025 17:56:33 -0600 Subject: [PATCH 47/63] Handle the case where the event editor is present but it has no GcalEvent yet Apparently the calendar's views hold a live event editor without an event, and only set an event when one is selected for editing. I think it would simplify things to only create the event editor when it is actually needed, with an event - it would avoid these checks. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 83 +++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index b8c74c67d..b1f2bcc01 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -357,29 +357,41 @@ widget_state_from_values (const GcalScheduleSectionValues *values) state->date_time_widgets_visible = !values->curr.all_day; state->time_format = values->time_format; - g_assert (values->curr.date_start); - state->date_time_start = g_date_time_ref (values->curr.date_start); - - if (values->curr.all_day) + if (values->curr.date_start && values->curr.date_end) { - /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't - * want to show the user a single-day all-day event as starting on Feb 1 and ending - * on Feb 2, for example. - * - * So, we maintain the "real" data in GcalScheduleValues with the $day+1 as iCalendar wants, - * but for display purposes, we subtract a day from the end date to show the expected thing to the - * user. When the widget changes, we add the day back - see gcal_schedule_values_set_end_date(). - * - * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. - * - * Keep in sync with gcal_schedule_values_set_end_date(). - */ - state->date_time_end = g_date_time_add_days (values->curr.date_end, -1); + state->date_time_start = g_date_time_ref (values->curr.date_start); + + if (values->curr.all_day) + { + /* While in iCalendar a single-day all-day event goes from $day to $day+1, we don't + * want to show the user a single-day all-day event as starting on Feb 1 and ending + * on Feb 2, for example. + * + * So, we maintain the "real" data in GcalScheduleValues with the $day+1 as iCalendar wants, + * but for display purposes, we subtract a day from the end date to show the expected thing to the + * user. When the widget changes, we add the day back - see gcal_schedule_values_set_end_date(). + * + * See https://bugzilla.gnome.org/show_bug.cgi?id=769300 for the original bug about this. + * + * Keep in sync with gcal_schedule_values_set_end_date(). + */ + state->date_time_end = g_date_time_add_days (values->curr.date_end, -1); + } + else + { + state->date_time_end = g_date_time_ref (values->curr.date_end); + } } else { - g_assert (values->curr.date_end); - state->date_time_end = g_date_time_ref (values->curr.date_end); + /* The event editor can be instantiated but not shown yet, and we may get a + * notification that the time_format changed. In that case, the event will not be + * set yet, and so the dates will not be set either - hence the test above for + * date_start and date_end not being NULL. Handle that by setting NULL dates for + * the widgets. + */ + state->date_time_start = NULL; + state->date_time_end = NULL; } if (values->curr.recur && values->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT) @@ -495,12 +507,17 @@ update_widgets (GcalScheduleSection *self, gcal_date_time_chooser_set_time_format (self->end_date_time_chooser, state->time_format); /* date */ - gcal_date_chooser_row_set_date (self->start_date_row, state->date_time_start); - gcal_date_chooser_row_set_date (self->end_date_row, state->date_time_end); + if (state->date_time_start) + { + gcal_date_chooser_row_set_date (self->start_date_row, state->date_time_start); + gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, state->date_time_start); + } - /* date-time */ - gcal_date_time_chooser_set_date_time (self->start_date_time_chooser, state->date_time_start); - gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, state->date_time_end); + if (state->date_time_end) + { + gcal_date_chooser_row_set_date (self->end_date_row, state->date_time_end); + gcal_date_time_chooser_set_date_time (self->end_date_time_chooser, state->date_time_end); + } /* Recurrences */ gtk_widget_set_visible (self->repeat_duration_combo, state->recurrence.duration_combo_visible); @@ -509,7 +526,11 @@ update_widgets (GcalScheduleSection *self, adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), state->recurrence.limit_type); gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), state->recurrence.limit_count); - gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), state->recurrence.limit_until); + + if (state->recurrence.limit_until) + { + gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), state->recurrence.limit_until); + } unblock_date_signals (self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); @@ -620,11 +641,17 @@ on_repeat_type_changed_cb (GtkWidget *widget, static void on_time_format_changed_cb (GcalScheduleSection *self) { - GcalTimeFormat time_format = gcal_context_get_time_format (self->context); + if (self->event) + { + /* Apparently we can be notified that the time format changed, even when + * there has not been an event set yet. This breaks the code downstream. + */ + GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_time_format (self->values, time_format); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_time_format (self->values, time_format); - update_from_section_values (self, updated); + update_from_section_values (self, updated); + } } -- GitLab From 63cf4374e42c68bd11840818f04d42b3cad65b5d Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 13 Mar 2025 17:57:40 -0600 Subject: [PATCH 48/63] Remove gcal_event_set/get_original_timezones() They are unused now. Part-of: --- src/core/gcal-event.c | 153 ------------------------------------------ src/core/gcal-event.h | 6 -- 2 files changed, 159 deletions(-) diff --git a/src/core/gcal-event.c b/src/core/gcal-event.c index 7f675123c..ea58686f3 100644 --- a/src/core/gcal-event.c +++ b/src/core/gcal-event.c @@ -1692,159 +1692,6 @@ gcal_event_get_recurrence (GcalEvent *self) return self->recurrence; } -/** - * gcal_event_get_original_timezones: - * @self: a #GcalEvent - * @out_start_timezone: (direction out)(nullable): the return location for the - * previously stored start date timezone - * @out_end_timezone: (direction out)(nullable): the return location for the - * previously stored end date timezone - * - * Retrieves the start and end date timezones previously stored by - * gcal_event_save_original_timezones(). - * - * If gcal_event_save_original_timezones() wasn't called before, - * it will use local timezones instead. - */ -void -gcal_event_get_original_timezones (GcalEvent *self, - GTimeZone **out_start_timezone, - GTimeZone **out_end_timezone) -{ - g_autoptr (GTimeZone) original_start_tz = NULL; - g_autoptr (GTimeZone) original_end_tz = NULL; - ICalComponent *icalcomp; - ICalProperty *property; - - g_return_if_fail (GCAL_IS_EVENT (self)); - g_assert (self->component != NULL); - - icalcomp = e_cal_component_get_icalcomponent (self->component); - - for (property = i_cal_component_get_first_property (icalcomp, I_CAL_X_PROPERTY); - property; - g_object_unref (property), property = i_cal_component_get_next_property (icalcomp, I_CAL_X_PROPERTY)) - { - const gchar *value; - - if (original_start_tz && original_end_tz) - { - g_object_unref (property); - break; - } - - if (g_strcmp0 (i_cal_property_get_x_name (property), "X-GNOME-CALENDAR-ORIGINAL-TZ-START") == 0) - { - value = i_cal_property_get_x (property); - original_start_tz = g_time_zone_new_identifier (value); - - GCAL_TRACE_MSG ("Found X-GNOME-CALENDAR-ORIGINAL-TZ-START=%s", value); - } - else if (g_strcmp0 (i_cal_property_get_x_name (property), "X-GNOME-CALENDAR-ORIGINAL-TZ-END") == 0) - { - value = i_cal_property_get_x (property); - original_end_tz = g_time_zone_new_identifier (value); - - GCAL_TRACE_MSG ("Found X-GNOME-CALENDAR-ORIGINAL-TZ-END=%s", value); - } - } - - if (!original_start_tz) - original_start_tz = g_time_zone_new_local (); - - if (!original_end_tz) - original_end_tz = g_time_zone_new_local (); - - g_assert (original_start_tz != NULL); - g_assert (original_end_tz != NULL); - - if (out_start_timezone) - *out_start_timezone = g_steal_pointer (&original_start_tz); - - if (out_end_timezone) - *out_end_timezone = g_steal_pointer (&original_end_tz); -} - -/** - * gcal_event_save_original_timezones: - * @self: a #GcalEvent - * - * Stores the current timezones of the start and end dates of @self - * into separate, GNOME Calendar specific fields. These fields can - * be used by gcal_event_get_original_timezones() to retrieve the - * previous timezones of the event later. - */ -void -gcal_event_save_original_timezones (GcalEvent *self) -{ - ICalComponent *icalcomp; - ICalProperty *property; - GTimeZone *tz; - gboolean has_original_start_tz; - gboolean has_original_end_tz; - - GCAL_ENTRY; - - g_return_if_fail (GCAL_IS_EVENT (self)); - g_assert (self->component != NULL); - - has_original_start_tz = FALSE; - has_original_end_tz = FALSE; - icalcomp = e_cal_component_get_icalcomponent (self->component); - - for (property = i_cal_component_get_first_property (icalcomp, I_CAL_X_PROPERTY); - property && (!has_original_start_tz || !has_original_end_tz); - g_object_unref (property), property = i_cal_component_get_next_property (icalcomp, I_CAL_X_PROPERTY)) - { - if (g_strcmp0 (i_cal_property_get_x_name (property), "X-GNOME-CALENDAR-ORIGINAL-TZ-START") == 0) - { - tz = g_date_time_get_timezone (self->dt_start); - i_cal_property_set_x (property, g_time_zone_get_identifier (tz)); - - GCAL_TRACE_MSG ("Reusing X-GNOME-CALENDAR-ORIGINAL-TZ-START property with %s", i_cal_property_get_x (property)); - - has_original_start_tz = TRUE; - } - else if (g_strcmp0 (i_cal_property_get_x_name (property), "X-GNOME-CALENDAR-ORIGINAL-TZ-END") == 0) - { - tz = g_date_time_get_timezone (self->dt_end); - i_cal_property_set_x (property, g_time_zone_get_identifier (tz)); - - GCAL_TRACE_MSG ("Reusing X-GNOME-CALENDAR-ORIGINAL-TZ-END property with %s", i_cal_property_get_x (property)); - - has_original_end_tz = TRUE; - } - } - - g_clear_object (&property); - - if (!has_original_start_tz) - { - tz = g_date_time_get_timezone (self->dt_start); - property = i_cal_property_new_x (g_time_zone_get_identifier (tz)); - i_cal_property_set_x_name (property, "X-GNOME-CALENDAR-ORIGINAL-TZ-START"); - - GCAL_TRACE_MSG ("Added new X-GNOME-CALENDAR-ORIGINAL-TZ-START property with %s", i_cal_property_get_x (property)); - - i_cal_component_take_property (icalcomp, property); - } - - if (!has_original_end_tz) - { - tz = g_date_time_get_timezone (self->dt_end); - property = i_cal_property_new_x (g_time_zone_get_identifier (tz)); - i_cal_property_set_x_name (property, "X-GNOME-CALENDAR-ORIGINAL-TZ-END"); - - GCAL_TRACE_MSG ("Added new X-GNOME-CALENDAR-ORIGINAL-TZ-END property with %s", i_cal_property_get_x (property)); - - i_cal_component_take_property (icalcomp, property); - } - - e_cal_component_commit_sequence (self->component); - - GCAL_EXIT; -} - /** * gcal_event_format_date: * @self: a #GcalEvent diff --git a/src/core/gcal-event.h b/src/core/gcal-event.h index 52fafe191..b9adbda6d 100644 --- a/src/core/gcal-event.h +++ b/src/core/gcal-event.h @@ -128,12 +128,6 @@ void gcal_event_set_recurrence (GcalEvent GcalRecurrence* gcal_event_get_recurrence (GcalEvent *self); -void gcal_event_save_original_timezones (GcalEvent *self); - -void gcal_event_get_original_timezones (GcalEvent *self, - GTimeZone **start_tz, - GTimeZone **end_tz); - gchar* gcal_event_format_date (GcalEvent *self); gboolean gcal_event_overlaps (GcalEvent *self, -- GitLab From 2d28a360722b35948de2c9edb13ee8e1080f237d Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 10 Apr 2025 19:33:39 -0600 Subject: [PATCH 49/63] Add helper function to get the GcalRecurrenceFrequency from the widgets Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index b1f2bcc01..eb5d9570a 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -623,14 +623,22 @@ on_repeat_duration_changed_cb (GtkWidget *widget, gtk_widget_set_visible (self->until_date_selector, active == 2); } +static GcalRecurrenceFrequency +get_recurrence_frequency (AdwComboRow *row) +{ + guint item = adw_combo_row_get_selected (row); + + g_assert (item >= GCAL_RECURRENCE_NO_REPEAT && item <= GCAL_RECURRENCE_OTHER); + + return (GcalRecurrenceFrequency) item; +} + static void on_repeat_type_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GcalRecurrenceFrequency frequency; - - frequency = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); + GcalRecurrenceFrequency frequency = get_recurrence_frequency (ADW_COMBO_ROW (widget)); if (frequency == GCAL_RECURRENCE_NO_REPEAT) adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), GCAL_RECURRENCE_FOREVER); -- GitLab From 13babc7a2e8631270a32aa4ccd79db9190830e2a Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 10 Apr 2025 19:35:47 -0600 Subject: [PATCH 50/63] Don't use magic numbers in on_repeat_duration_changed_cb() Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index eb5d9570a..6c677aa47 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -617,10 +617,10 @@ on_repeat_duration_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - gint active = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); + GcalRecurrenceLimitType limit_type = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); - gtk_widget_set_visible (self->number_of_occurrences_spin, active == 1); - gtk_widget_set_visible (self->until_date_selector, active == 2); + gtk_widget_set_visible (self->number_of_occurrences_spin, limit_type == GCAL_RECURRENCE_COUNT); + gtk_widget_set_visible (self->until_date_selector, limit_type == GCAL_RECURRENCE_UNTIL); } static GcalRecurrenceFrequency -- GitLab From ffd66ff084859c11601314c159c398da677339ee Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Thu, 10 Apr 2025 19:38:41 -0600 Subject: [PATCH 51/63] Add helper function to get GcalRecurrenceLimitType from the widgets Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 6c677aa47..390bd05b2 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -612,12 +612,22 @@ on_end_date_time_changed_cb (GtkWidget *widget, update_from_section_values (self, updated); } +static GcalRecurrenceLimitType +get_recurence_limit_type (AdwComboRow *row) +{ + guint item = adw_combo_row_get_selected (row); + + g_assert (item >= GCAL_RECURRENCE_FOREVER && item <= GCAL_RECURRENCE_UNTIL); + + return (GcalRecurrenceLimitType) item; +} + static void on_repeat_duration_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GcalRecurrenceLimitType limit_type = adw_combo_row_get_selected (ADW_COMBO_ROW (widget)); + GcalRecurrenceLimitType limit_type = get_recurence_limit_type (ADW_COMBO_ROW (widget)); gtk_widget_set_visible (self->number_of_occurrences_spin, limit_type == GCAL_RECURRENCE_COUNT); gtk_widget_set_visible (self->until_date_selector, limit_type == GCAL_RECURRENCE_UNTIL); -- GitLab From 05fed3d9c6b94baafc58e06de1218dbea9b887e0 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 08:57:13 -0600 Subject: [PATCH 52/63] Use get_recurrence_frequency() in all places Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 390bd05b2..54c5323d5 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -634,9 +634,9 @@ on_repeat_duration_changed_cb (GtkWidget *widget, } static GcalRecurrenceFrequency -get_recurrence_frequency (AdwComboRow *row) +get_recurrence_frequency (GcalScheduleSection *self) { - guint item = adw_combo_row_get_selected (row); + guint item = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); g_assert (item >= GCAL_RECURRENCE_NO_REPEAT && item <= GCAL_RECURRENCE_OTHER); @@ -648,7 +648,7 @@ on_repeat_type_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GcalRecurrenceFrequency frequency = get_recurrence_frequency (ADW_COMBO_ROW (widget)); + GcalRecurrenceFrequency frequency = get_recurrence_frequency (self); if (frequency == GCAL_RECURRENCE_NO_REPEAT) adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), GCAL_RECURRENCE_FOREVER); @@ -728,7 +728,7 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, /* Check Repeat popover and set recurrence-rules accordingly */ - freq = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_combo)); + freq = get_recurrence_frequency (self); if (freq != GCAL_RECURRENCE_NO_REPEAT) { -- GitLab From 74e784fb92195b16aca2236c7da910f819e66a2c Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 08:59:41 -0600 Subject: [PATCH 53/63] Use get_recurence_limit_type() in all places Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 54c5323d5..6496c54df 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -613,9 +613,9 @@ on_end_date_time_changed_cb (GtkWidget *widget, } static GcalRecurrenceLimitType -get_recurence_limit_type (AdwComboRow *row) +get_recurence_limit_type (GcalScheduleSection *self) { - guint item = adw_combo_row_get_selected (row); + guint item = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_duration_combo)); g_assert (item >= GCAL_RECURRENCE_FOREVER && item <= GCAL_RECURRENCE_UNTIL); @@ -627,7 +627,7 @@ on_repeat_duration_changed_cb (GtkWidget *widget, GParamSpec *pspec, GcalScheduleSection *self) { - GcalRecurrenceLimitType limit_type = get_recurence_limit_type (ADW_COMBO_ROW (widget)); + GcalRecurrenceLimitType limit_type = get_recurence_limit_type (self); gtk_widget_set_visible (self->number_of_occurrences_spin, limit_type == GCAL_RECURRENCE_COUNT); gtk_widget_set_visible (self->until_date_selector, limit_type == GCAL_RECURRENCE_UNTIL); @@ -736,7 +736,7 @@ gcal_schedule_section_apply_to_event (GcalScheduleSection *self, recur = gcal_recurrence_new (); recur->frequency = freq; - recur->limit_type = adw_combo_row_get_selected (ADW_COMBO_ROW (self->repeat_duration_combo)); + recur->limit_type = get_recurence_limit_type (self); if (recur->limit_type == GCAL_RECURRENCE_UNTIL) recur->limit.until = g_date_time_ref (gcal_date_selector_get_date (GCAL_DATE_SELECTOR (self->until_date_selector))); -- GitLab From 52a7021241afb98c07d52d69846b1d43689dd852 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 09:30:42 -0600 Subject: [PATCH 54/63] Set the recurrence frequency via an action function, not by poking the widgets directly Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 57 ++++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 6496c54df..f94d3da2b 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -347,6 +347,44 @@ gcal_schedule_section_values_set_end_date_time (const GcalScheduleSectionValues return copy; } +static GcalRecurrence * +recur_change_frequency (GcalRecurrence *old_recur, GcalRecurrenceFrequency frequency) +{ + if (frequency == GCAL_RECURRENCE_NO_REPEAT) + { + return NULL; + } + else + { + GcalRecurrence *new_recur; + + if (old_recur) + { + new_recur = gcal_recurrence_copy (old_recur); + } + else + { + new_recur = gcal_recurrence_new (); + } + + new_recur->frequency = frequency; + + return new_recur; + } +} + +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_recur_frequency (const GcalScheduleSectionValues *values, + GcalRecurrenceFrequency frequency) +{ + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalRecurrence *new_recur = recur_change_frequency (copy->curr.recur, frequency); + g_clear_pointer (©->curr.recur, gcal_recurrence_unref); + copy->curr.recur = new_recur; + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleSectionValues *values) { @@ -649,11 +687,8 @@ on_repeat_type_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GcalRecurrenceFrequency frequency = get_recurrence_frequency (self); - - if (frequency == GCAL_RECURRENCE_NO_REPEAT) - adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), GCAL_RECURRENCE_FOREVER); - - gtk_widget_set_visible (self->repeat_duration_combo, frequency != GCAL_RECURRENCE_NO_REPEAT); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recur_frequency (self->values, frequency); + update_from_section_values (self, updated); } static void @@ -1238,6 +1273,16 @@ test_43_switching_to_all_day_preserves_timezones (void) g_assert (g_date_time_equal (not_all_day_values->curr.date_end, expected_end_date2)); } +static void +test_recur_changes_to_no_repeat (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GcalRecurrence) new_recur = recur_change_frequency (old_recur, GCAL_RECURRENCE_NO_REPEAT); + g_assert_null (new_recur); +} + void gcal_schedule_section_add_tests (void) { @@ -1253,4 +1298,6 @@ gcal_schedule_section_add_tests (void) test_all_day_displays_sensible_dates_and_roundtrips); g_test_add_func ("/event_editor/schedule_section/43_switching_to_all_day_preserves_timezones", test_43_switching_to_all_day_preserves_timezones); + g_test_add_func ("/event_editor/schedule_section/recur_changes_to_no_repeat", + test_recur_changes_to_no_repeat); } -- GitLab From dfe38af9ac0a366703686d75b7d84df5af7b4309 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 10:13:45 -0600 Subject: [PATCH 55/63] Set the recurrence limit type via an action Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 113 +++++++++++++------ 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index f94d3da2b..d9eb874d5 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -352,6 +352,7 @@ recur_change_frequency (GcalRecurrence *old_recur, GcalRecurrenceFrequency frequ { if (frequency == GCAL_RECURRENCE_NO_REPEAT) { + /* Invariant: GCAL_RECURRENCE_NO_REPEAT is reduced to a NULL recurrence */ return NULL; } else @@ -373,6 +374,47 @@ recur_change_frequency (GcalRecurrence *old_recur, GcalRecurrenceFrequency frequ } } +static GcalRecurrence * +recur_change_limit_type (GcalRecurrence *old_recur, GcalRecurrenceLimitType limit_type, GDateTime *date_start) +{ + /* An old recurrence would already be present with something other than GCAL_RECURRENCE_NO_REPEAT */ + g_assert (old_recur != NULL); + g_assert (old_recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + + GcalRecurrence *new_recur = gcal_recurrence_copy (old_recur); + + if (limit_type == old_recur->limit_type) + { + return new_recur; + } + else + { + new_recur->limit_type = limit_type; + + switch (limit_type) + { + case GCAL_RECURRENCE_FOREVER: + break; + + case GCAL_RECURRENCE_COUNT: + /* Other policies are possible. This is just "more than once". */ + new_recur->limit.count = 2; + break; + + case GCAL_RECURRENCE_UNTIL: + /* Again, other policies are possible. For now, leave the decision to the user. */ + g_assert (date_start != NULL); + new_recur->limit.until = g_date_time_ref (date_start); + break; + + default: + g_assert_not_reached (); + } + } + + return new_recur; +} + static GcalScheduleSectionValues * gcal_schedule_section_values_set_recur_frequency (const GcalScheduleSectionValues *values, GcalRecurrenceFrequency frequency) @@ -385,6 +427,18 @@ gcal_schedule_section_values_set_recur_frequency (const GcalScheduleSectionValue return copy; } +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_recur_limit_type (const GcalScheduleSectionValues *values, + GcalRecurrenceLimitType limit_type) +{ + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalRecurrence *new_recur = recur_change_limit_type (copy->curr.recur, limit_type, values->curr.date_start); + g_clear_pointer (©->curr.recur, gcal_recurrence_unref); + copy->curr.recur = new_recur; + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleSectionValues *values) { @@ -666,9 +720,8 @@ on_repeat_duration_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GcalRecurrenceLimitType limit_type = get_recurence_limit_type (self); - - gtk_widget_set_visible (self->number_of_occurrences_spin, limit_type == GCAL_RECURRENCE_COUNT); - gtk_widget_set_visible (self->until_date_selector, limit_type == GCAL_RECURRENCE_UNTIL); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recur_limit_type (self->values, limit_type); + update_from_section_values (self, updated); } static GcalRecurrenceFrequency @@ -753,46 +806,29 @@ static void gcal_schedule_section_apply_to_event (GcalScheduleSection *self, GcalEvent *event) { - GcalRecurrenceFrequency freq; - GCAL_ENTRY; gcal_event_set_all_day (event, self->values->curr.all_day); gcal_event_set_date_start (event, self->values->curr.date_start); gcal_event_set_date_end (event, self->values->curr.date_end); - /* Check Repeat popover and set recurrence-rules accordingly */ - - freq = get_recurrence_frequency (self); - - if (freq != GCAL_RECURRENCE_NO_REPEAT) + /* Only apply the new recurrence if it's different from the old one. + * We don't unconditionally set the recurrence since remove_recurrence_properties() + * will actually remove all the rrules, not just *a* unique one. + * + * That is, here we allow for future support for more than one rrule + * in the event, given the current capabilities of gnome-calendar. + * + */ + if (!gcal_recurrence_is_equal (self->values->curr.recur, gcal_event_get_recurrence (event))) { - g_autoptr (GcalRecurrence) recur = NULL; - - recur = gcal_recurrence_new (); - recur->frequency = freq; - recur->limit_type = get_recurence_limit_type (self); - - if (recur->limit_type == GCAL_RECURRENCE_UNTIL) - recur->limit.until = g_date_time_ref (gcal_date_selector_get_date (GCAL_DATE_SELECTOR (self->until_date_selector))); - else if (recur->limit_type == GCAL_RECURRENCE_COUNT) - recur->limit.count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); + remove_recurrence_properties (event); - /* Only apply the new recurrence if it's different from the old one */ - if (!gcal_recurrence_is_equal (self->values->curr.recur, recur)) + if (self->values->curr.recur) { - /* Remove the previous recurrence... */ - remove_recurrence_properties (event); - - /* ... and set the new one */ - gcal_event_set_recurrence (event, recur); + gcal_event_set_recurrence (event, self->values->curr.recur); } } - else - { - /* When NO_REPEAT is set, make sure to remove the old recurrent */ - remove_recurrence_properties (event); - } GCAL_EXIT; } @@ -1283,6 +1319,17 @@ test_recur_changes_to_no_repeat (void) g_assert_null (new_recur); } +static void +test_recur_changes_limit_type_to_count (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_COUNT, NULL); + g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_COUNT); + g_assert_cmpint (new_recur->limit.count, ==, 2); +} + void gcal_schedule_section_add_tests (void) { @@ -1300,4 +1347,6 @@ gcal_schedule_section_add_tests (void) test_43_switching_to_all_day_preserves_timezones); g_test_add_func ("/event_editor/schedule_section/recur_changes_to_no_repeat", test_recur_changes_to_no_repeat); + g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_count", + test_recur_changes_limit_type_to_count); } -- GitLab From 98afcf31957218aba4d4cda492eefd4be8a276f8 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 10:14:35 -0600 Subject: [PATCH 56/63] Remove superfluous "inline" This is not a hot code path and the keyword just adds noise. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index d9eb874d5..16b5cc869 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -540,7 +540,7 @@ widget_state_free (WidgetState *state) * Auxiliary methods */ -static inline gboolean +static gboolean all_day_selected (GcalScheduleSection *self) { const gchar *active = adw_toggle_group_get_active_name (self->schedule_type_toggle_group); -- GitLab From ca5bf2269f9262d61e50196df6f0ad4444f8c423 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 10:46:18 -0600 Subject: [PATCH 57/63] Add test for setting the recurrence limit to UNTIL Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 16b5cc869..19aaf1b17 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -1330,6 +1330,19 @@ test_recur_changes_limit_type_to_count (void) g_assert_cmpint (new_recur->limit.count, ==, 2); } +static void +test_recur_changes_limit_type_to_until (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250411T00:00:00-06:00", NULL); + + g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_UNTIL, date); + g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_UNTIL); + g_assert (g_date_time_equal (new_recur->limit.until, date)); +} + void gcal_schedule_section_add_tests (void) { @@ -1349,4 +1362,6 @@ gcal_schedule_section_add_tests (void) test_recur_changes_to_no_repeat); g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_count", test_recur_changes_limit_type_to_count); + g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_until", + test_recur_changes_limit_type_to_until); } -- GitLab From d65e1ff216e8142c6b540e8202981ee8492800f6 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 12:23:51 -0600 Subject: [PATCH 58/63] Add tests for the visibility of the widgets to control recurence limits Since changing the limit type changes the visibility of the "count" or "until" widgets, we need to test that logic. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 65 +++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 19aaf1b17..5de178939 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -79,6 +79,8 @@ typedef struct struct { gboolean duration_combo_visible; + gboolean number_of_occurrences_visible; + gboolean until_date_visible; GcalRecurrenceFrequency frequency; GcalRecurrenceLimitType limit_type; guint limit_count; @@ -496,9 +498,11 @@ widget_state_from_values (const GcalScheduleSectionValues *values) { case GCAL_RECURRENCE_COUNT: state->recurrence.limit_count = values->curr.recur->limit.count; + state->recurrence.number_of_occurrences_visible = TRUE; break; case GCAL_RECURRENCE_UNTIL: + state->recurrence.until_date_visible = TRUE; state->recurrence.limit_until = g_date_time_ref (values->curr.recur->limit.until); break; @@ -513,6 +517,8 @@ widget_state_from_values (const GcalScheduleSectionValues *values) else { state->recurrence.duration_combo_visible = FALSE; + state->recurrence.number_of_occurrences_visible = FALSE; + state->recurrence.until_date_visible = FALSE; state->recurrence.frequency = GCAL_RECURRENCE_NO_REPEAT; state->recurrence.limit_type = GCAL_RECURRENCE_FOREVER; state->recurrence.limit_count = 0; @@ -617,13 +623,18 @@ update_widgets (GcalScheduleSection *self, adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_combo), state->recurrence.frequency); adw_combo_row_set_selected (ADW_COMBO_ROW (self->repeat_duration_combo), state->recurrence.limit_type); - gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), state->recurrence.limit_count); - - if (state->recurrence.limit_until) + gtk_widget_set_visible (self->until_date_selector, state->recurrence.until_date_visible); + if (state->recurrence.until_date_visible) { gcal_date_selector_set_date (GCAL_DATE_SELECTOR (self->until_date_selector), state->recurrence.limit_until); } + gtk_widget_set_visible (self->number_of_occurrences_spin, state->recurrence.number_of_occurrences_visible); + if (state->recurrence.number_of_occurrences_visible) + { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), state->recurrence.limit_count); + } + unblock_date_signals (self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } @@ -1343,6 +1354,50 @@ test_recur_changes_limit_type_to_until (void) g_assert (g_date_time_equal (new_recur->limit.until, date)); } +static void +test_turning_on_recurrence_count_turns_on_its_widget (void) +{ + /* Start with some configuration */ + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); + + /* Turn on recurrence */ + g_autoptr (GcalScheduleSectionValues) with_recur = + gcal_schedule_section_values_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); + + /* Set it to "count" */ + g_autoptr (GcalScheduleSectionValues) with_count = + gcal_schedule_section_values_set_recur_limit_type (with_recur, GCAL_RECURRENCE_COUNT); + + g_autoptr (WidgetState) state = widget_state_from_values (with_count); + g_assert_true (state->recurrence.duration_combo_visible); + g_assert_true (state->recurrence.number_of_occurrences_visible); + g_assert_false (state->recurrence.until_date_visible); +} + +static void +test_turning_on_recurrence_until_turns_on_its_widget (void) +{ + /* Start with some configuration */ + g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); + + /* Turn on recurrence */ + g_autoptr (GcalScheduleSectionValues) with_recur = + gcal_schedule_section_values_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); + + /* Set it to "count" */ + g_autoptr (GcalScheduleSectionValues) with_until = + gcal_schedule_section_values_set_recur_limit_type (with_recur, GCAL_RECURRENCE_UNTIL); + + g_autoptr (WidgetState) state = widget_state_from_values (with_until); + g_assert_true (state->recurrence.duration_combo_visible); + g_assert_false (state->recurrence.number_of_occurrences_visible); + g_assert_true (state->recurrence.until_date_visible); +} + void gcal_schedule_section_add_tests (void) { @@ -1364,4 +1419,8 @@ gcal_schedule_section_add_tests (void) test_recur_changes_limit_type_to_count); g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_until", test_recur_changes_limit_type_to_until); + g_test_add_func ("/event_editor/schedule_section/turning_on_recurrence_count_turns_on_its_widget", + test_turning_on_recurrence_count_turns_on_its_widget); + g_test_add_func ("/event_editor/schedule_section/turning_on_recurrence_until_turns_on_its_widget", + test_turning_on_recurrence_until_turns_on_its_widget); } -- GitLab From 7bb6549cb64566218e69f2c25f26213f4df885ab Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 13:43:27 -0600 Subject: [PATCH 59/63] Handle the recurrence count with an action, not via the widgets Part-of: --- .../event-editor/gcal-schedule-section.blp | 1 + src/gui/event-editor/gcal-schedule-section.c | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.blp b/src/gui/event-editor/gcal-schedule-section.blp index 8baf77ef9..e1529c58c 100644 --- a/src/gui/event-editor/gcal-schedule-section.blp +++ b/src/gui/event-editor/gcal-schedule-section.blp @@ -94,6 +94,7 @@ template $GcalScheduleSection: Box { numeric: true; adjustment: number_of_occurrences_adjustment; valign: center; + value-changed => $on_number_of_occurrences_changed_cb() swapped; } } diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 5de178939..b021ce6d3 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -124,6 +124,8 @@ static void on_schedule_type_changed_cb (GtkWidget GParamSpec *pspec, GcalScheduleSection *self); +static void on_number_of_occurrences_changed_cb (GcalScheduleSection *self); + static GcalScheduleValues gcal_schedule_values_copy (const GcalScheduleValues *values) { @@ -441,6 +443,22 @@ gcal_schedule_section_values_set_recur_limit_type (const GcalScheduleSectionValu return copy; } +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_recurrence_count (const GcalScheduleSectionValues *values, + guint count) +{ + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + + /* An old recurrence would already be present */ + g_assert (copy->curr.recur != NULL); + g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_COUNT); + + copy->curr.recur->limit.count = count; + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleSectionValues *values) { @@ -591,6 +609,7 @@ update_widgets (GcalScheduleSection *self, WidgetState *state) { g_signal_handlers_block_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); + g_signal_handlers_block_by_func (self->number_of_occurrences_spin, on_number_of_occurrences_changed_cb, self); block_date_signals (self); adw_toggle_group_set_active_name (self->schedule_type_toggle_group, @@ -632,10 +651,12 @@ update_widgets (GcalScheduleSection *self, gtk_widget_set_visible (self->number_of_occurrences_spin, state->recurrence.number_of_occurrences_visible); if (state->recurrence.number_of_occurrences_visible) { + g_print ("update widgets: limit_count = %u\n", state->recurrence.limit_count); gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->number_of_occurrences_spin), state->recurrence.limit_count); } unblock_date_signals (self); + g_signal_handlers_unblock_by_func (self->number_of_occurrences_spin, on_number_of_occurrences_changed_cb, self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } @@ -715,6 +736,14 @@ on_end_date_time_changed_cb (GtkWidget *widget, update_from_section_values (self, updated); } +static void +on_number_of_occurrences_changed_cb (GcalScheduleSection *self) +{ + guint count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recurrence_count (self->values, count); + update_from_section_values (self, updated); +} + static GcalRecurrenceLimitType get_recurence_limit_type (GcalScheduleSection *self) { @@ -1017,6 +1046,7 @@ gcal_schedule_section_class_init (GcalScheduleSectionClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_end_date_time_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_repeat_duration_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_repeat_type_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_number_of_occurrences_changed_cb); } static void -- GitLab From 8c0281aef5af698a8e0bb96a4d5e001fab40ecbe Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 13:44:26 -0600 Subject: [PATCH 60/63] Copy the recurrence, don't just ref it GcalRecurrence is shared/mutable. So, if we have curr.recur = ref(event.recur) orig.recur = ref(curr.recur) then recurrence_changed() will return false, since both orig.recur and curr.recur will point to the same object. We need fresh copies. Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index b021ce6d3..227ff923b 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -134,7 +134,7 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) copy.all_day = values->all_day; copy.date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; copy.date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; - copy.recur = values->recur ? gcal_recurrence_ref (values->recur) : NULL; + copy.recur = values->recur ? gcal_recurrence_copy (values->recur) : NULL; return copy; } @@ -1099,7 +1099,7 @@ gcal_schedule_section_values_from_event (GcalEvent *event, if (recur) { - values.recur = gcal_recurrence_ref (recur); + values.recur = gcal_recurrence_copy (recur); } else { -- GitLab From e9a626ed40c4c8d8d89a6eb2ec45aa8f46d6b298 Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 14:27:48 -0600 Subject: [PATCH 61/63] Handle the recurrence until-date with an action, not via the widgets Part-of: --- .../event-editor/gcal-schedule-section.blp | 1 + src/gui/event-editor/gcal-schedule-section.c | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/gui/event-editor/gcal-schedule-section.blp b/src/gui/event-editor/gcal-schedule-section.blp index e1529c58c..fcd450f70 100644 --- a/src/gui/event-editor/gcal-schedule-section.blp +++ b/src/gui/event-editor/gcal-schedule-section.blp @@ -106,6 +106,7 @@ template $GcalScheduleSection: Box { $GcalDateSelector until_date_selector { visible: false; valign: center; + notify::date => $on_until_date_changed_cb() swapped; } } } diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 227ff923b..5a94d359a 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -125,6 +125,7 @@ static void on_schedule_type_changed_cb (GtkWidget GcalScheduleSection *self); static void on_number_of_occurrences_changed_cb (GcalScheduleSection *self); +static void on_until_date_changed_cb (GcalScheduleSection *self); static GcalScheduleValues gcal_schedule_values_copy (const GcalScheduleValues *values) @@ -459,6 +460,22 @@ gcal_schedule_section_values_set_recurrence_count (const GcalScheduleSectionValu return copy; } +static GcalScheduleSectionValues * +gcal_schedule_section_values_set_recurrence_until (const GcalScheduleSectionValues *values, + GDateTime *until) +{ + GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + + /* An old recurrence would already be present */ + g_assert (copy->curr.recur != NULL); + g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_UNTIL); + + gcal_set_date_time (©->curr.recur->limit.until, until); + + return copy; +} + static WidgetState * widget_state_from_values (const GcalScheduleSectionValues *values) { @@ -610,6 +627,7 @@ update_widgets (GcalScheduleSection *self, { g_signal_handlers_block_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); g_signal_handlers_block_by_func (self->number_of_occurrences_spin, on_number_of_occurrences_changed_cb, self); + g_signal_handlers_block_by_func (self->until_date_selector, on_until_date_changed_cb, self); block_date_signals (self); adw_toggle_group_set_active_name (self->schedule_type_toggle_group, @@ -656,6 +674,7 @@ update_widgets (GcalScheduleSection *self, } unblock_date_signals (self); + g_signal_handlers_unblock_by_func (self->until_date_selector, on_until_date_changed_cb, self); g_signal_handlers_unblock_by_func (self->number_of_occurrences_spin, on_number_of_occurrences_changed_cb, self); g_signal_handlers_unblock_by_func (self->schedule_type_toggle_group, on_schedule_type_changed_cb, self); } @@ -744,6 +763,14 @@ on_number_of_occurrences_changed_cb (GcalScheduleSection *self) update_from_section_values (self, updated); } +static void +on_until_date_changed_cb (GcalScheduleSection *self) +{ + GDateTime *until = gcal_date_selector_get_date (GCAL_DATE_SELECTOR (self->until_date_selector)); + GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recurrence_until (self->values, until); + update_from_section_values (self, updated); +} + static GcalRecurrenceLimitType get_recurence_limit_type (GcalScheduleSection *self) { @@ -1047,6 +1074,7 @@ gcal_schedule_section_class_init (GcalScheduleSectionClass *klass) gtk_widget_class_bind_template_callback (widget_class, on_repeat_duration_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_repeat_type_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_number_of_occurrences_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_until_date_changed_cb); } static void -- GitLab From 8a653ec99d8162cc34dfe5a2dc7e5b7d4295948e Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 18:54:11 -0600 Subject: [PATCH 62/63] Rename GcalScheduleSectionValues -> GcalEventSchedule And correspondingly, gcal_schedule_section_values_*() -> gcal_event_schedule_*() Part-of: --- src/gui/event-editor/gcal-schedule-section.c | 250 +++++++++---------- src/gui/event-editor/gcal-schedule-section.h | 10 +- 2 files changed, 130 insertions(+), 130 deletions(-) diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 5a94d359a..6dc7ba632 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -37,7 +37,7 @@ struct _GcalScheduleSection { GtkBox parent; - GcalScheduleSectionValues *values; + GcalEventSchedule *values; AdwToggleGroup *schedule_type_toggle_group; AdwPreferencesGroup *start_date_group; @@ -57,10 +57,10 @@ struct _GcalScheduleSection GcalEventEditorFlags flags; }; -/* Desired state of the widgets, computed from GcalScheduleValues. +/* Desired state of the widgets, computed from GcalEventSchedule. * * There is some subtle logic to decide how to update the widgets based on how - * each of them causes the GcalScheduleValues to be modified. We encapsulate + * each of them causes the GcalEventSchedule to be modified. We encapsulate * the desired state in this struct, so we can have tests for it. */ typedef struct @@ -72,7 +72,7 @@ typedef struct GcalTimeFormat time_format; /* Note that the displayed date/time may not correspond to the real data in the - * GcalScheduleValues. See the comment in widget_state_from_values(). + * GcalEventSchedule. See the comment in widget_state_from_values(). */ GDateTime *date_time_start; GDateTime *date_time_end; @@ -140,10 +140,10 @@ gcal_schedule_values_copy (const GcalScheduleValues *values) return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_copy (const GcalScheduleSectionValues *values) +static GcalEventSchedule * +gcal_event_schedule_copy (const GcalEventSchedule *values) { - GcalScheduleSectionValues *copy = g_new0 (GcalScheduleSectionValues, 1); + GcalEventSchedule *copy = g_new0 (GcalEventSchedule, 1); copy->orig = gcal_schedule_values_copy (&values->orig); copy->curr = gcal_schedule_values_copy (&values->curr); @@ -163,22 +163,22 @@ gcal_schedule_values_free (GcalScheduleValues *values) } /** - * gcal_schedule_section_values_free(): + * gcal_event_schedule_free(): * * Frees the contents of @values. */ void -gcal_schedule_section_values_free (GcalScheduleSectionValues *values) +gcal_event_schedule_free (GcalEventSchedule *values) { gcal_schedule_values_free (&values->orig); gcal_schedule_values_free (&values->curr); g_free (values); } -static GcalScheduleSectionValues* -gcal_schedule_section_values_set_all_day (const GcalScheduleSectionValues *values, gboolean all_day) +static GcalEventSchedule* +gcal_event_schedule_set_all_day (const GcalEventSchedule *values, gboolean all_day) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); if (all_day == values->curr.all_day) { @@ -257,10 +257,10 @@ gcal_schedule_section_values_set_all_day (const GcalScheduleSectionValues *value return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_time_format (const GcalScheduleSectionValues *values, GcalTimeFormat time_format) +static GcalEventSchedule * +gcal_event_schedule_set_time_format (const GcalEventSchedule *values, GcalTimeFormat time_format) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); copy->time_format = time_format; return copy; @@ -269,10 +269,10 @@ gcal_schedule_section_values_set_time_format (const GcalScheduleSectionValues *v /* The start_date_row widget has changed. We need to sync it to the values and * adjust the other values based on it. */ -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_start_date (const GcalScheduleSectionValues *values, GDateTime *start) +static GcalEventSchedule * +gcal_event_schedule_set_start_date (const GcalEventSchedule *values, GDateTime *start) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); gcal_set_date_time (©->curr.date_start, start); @@ -287,10 +287,10 @@ gcal_schedule_section_values_set_start_date (const GcalScheduleSectionValues *va /* The end_date_row widget has changed. We need to sync it to the values and * adjust the other values based on it. */ -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_end_date (const GcalScheduleSectionValues *values, GDateTime *end) +static GcalEventSchedule * +gcal_event_schedule_set_end_date (const GcalEventSchedule *values, GDateTime *end) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); if (copy->curr.all_day) { @@ -314,10 +314,10 @@ gcal_schedule_section_values_set_end_date (const GcalScheduleSectionValues *valu return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_start_date_time (const GcalScheduleSectionValues *values, GDateTime *start) +static GcalEventSchedule * +gcal_event_schedule_set_start_date_time (const GcalEventSchedule *values, GDateTime *start) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); gcal_set_date_time (©->curr.date_start, start); @@ -333,10 +333,10 @@ gcal_schedule_section_values_set_start_date_time (const GcalScheduleSectionValue return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_end_date_time (const GcalScheduleSectionValues *values, GDateTime *end) +static GcalEventSchedule * +gcal_event_schedule_set_end_date_time (const GcalEventSchedule *values, GDateTime *end) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); gcal_set_date_time (©->curr.date_end, end); @@ -420,11 +420,11 @@ recur_change_limit_type (GcalRecurrence *old_recur, GcalRecurrenceLimitType limi return new_recur; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_recur_frequency (const GcalScheduleSectionValues *values, - GcalRecurrenceFrequency frequency) +static GcalEventSchedule * +gcal_event_schedule_set_recur_frequency (const GcalEventSchedule *values, + GcalRecurrenceFrequency frequency) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); GcalRecurrence *new_recur = recur_change_frequency (copy->curr.recur, frequency); g_clear_pointer (©->curr.recur, gcal_recurrence_unref); copy->curr.recur = new_recur; @@ -432,11 +432,11 @@ gcal_schedule_section_values_set_recur_frequency (const GcalScheduleSectionValue return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_recur_limit_type (const GcalScheduleSectionValues *values, - GcalRecurrenceLimitType limit_type) +static GcalEventSchedule * +gcal_event_schedule_set_recur_limit_type (const GcalEventSchedule *values, + GcalRecurrenceLimitType limit_type) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); GcalRecurrence *new_recur = recur_change_limit_type (copy->curr.recur, limit_type, values->curr.date_start); g_clear_pointer (©->curr.recur, gcal_recurrence_unref); copy->curr.recur = new_recur; @@ -444,11 +444,11 @@ gcal_schedule_section_values_set_recur_limit_type (const GcalScheduleSectionValu return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_recurrence_count (const GcalScheduleSectionValues *values, - guint count) +static GcalEventSchedule * +gcal_event_schedule_set_recurrence_count (const GcalEventSchedule *values, + guint count) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); /* An old recurrence would already be present */ g_assert (copy->curr.recur != NULL); @@ -460,11 +460,11 @@ gcal_schedule_section_values_set_recurrence_count (const GcalScheduleSectionValu return copy; } -static GcalScheduleSectionValues * -gcal_schedule_section_values_set_recurrence_until (const GcalScheduleSectionValues *values, - GDateTime *until) +static GcalEventSchedule * +gcal_event_schedule_set_recurrence_until (const GcalEventSchedule *values, + GDateTime *until) { - GcalScheduleSectionValues *copy = gcal_schedule_section_values_copy (values); + GcalEventSchedule *copy = gcal_event_schedule_copy (values); /* An old recurrence would already be present */ g_assert (copy->curr.recur != NULL); @@ -477,7 +477,7 @@ gcal_schedule_section_values_set_recurrence_until (const GcalScheduleSectionValu } static WidgetState * -widget_state_from_values (const GcalScheduleSectionValues *values) +widget_state_from_values (const GcalEventSchedule *values) { WidgetState *state = g_new0 (WidgetState, 1); @@ -496,7 +496,7 @@ widget_state_from_values (const GcalScheduleSectionValues *values) * want to show the user a single-day all-day event as starting on Feb 1 and ending * on Feb 2, for example. * - * So, we maintain the "real" data in GcalScheduleValues with the $day+1 as iCalendar wants, + * So, we maintain the "real" data in GcalEventSchedule with the $day+1 as iCalendar wants, * but for display purposes, we subtract a day from the end date to show the expected thing to the * user. When the widget changes, we add the day back - see gcal_schedule_values_set_end_date(). * @@ -692,10 +692,10 @@ refresh (GcalScheduleSection *self) * This will free the current values and replace them with new ones. */ static void -update_from_section_values (GcalScheduleSection *self, - GcalScheduleSectionValues *values) +update_from_event_schedule (GcalScheduleSection *self, + GcalEventSchedule *values) { - gcal_schedule_section_values_free (self->values); + gcal_event_schedule_free (self->values); self->values = values; refresh (self); @@ -711,8 +711,8 @@ on_schedule_type_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { gboolean all_day = all_day_selected (self); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_all_day (self->values, all_day); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_all_day (self->values, all_day); + update_from_event_schedule (self, updated); } static void @@ -721,8 +721,8 @@ on_start_date_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *start = gcal_date_chooser_row_get_date (self->start_date_row); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_start_date (self->values, start); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_start_date (self->values, start); + update_from_event_schedule (self, updated); } static void @@ -731,8 +731,8 @@ on_end_date_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *end = gcal_date_chooser_row_get_date (self->end_date_row); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_end_date (self->values, end); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_end_date (self->values, end); + update_from_event_schedule (self, updated); } static void @@ -741,8 +741,8 @@ on_start_date_time_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *start = gcal_date_time_chooser_get_date_time (self->start_date_time_chooser); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_start_date_time (self->values, start); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_start_date_time (self->values, start); + update_from_event_schedule (self, updated); } static void @@ -751,24 +751,24 @@ on_end_date_time_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GDateTime *end = gcal_date_time_chooser_get_date_time (self->end_date_time_chooser); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_end_date_time (self->values, end); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_end_date_time (self->values, end); + update_from_event_schedule (self, updated); } static void on_number_of_occurrences_changed_cb (GcalScheduleSection *self) { guint count = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->number_of_occurrences_spin)); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recurrence_count (self->values, count); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_recurrence_count (self->values, count); + update_from_event_schedule (self, updated); } static void on_until_date_changed_cb (GcalScheduleSection *self) { GDateTime *until = gcal_date_selector_get_date (GCAL_DATE_SELECTOR (self->until_date_selector)); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recurrence_until (self->values, until); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_recurrence_until (self->values, until); + update_from_event_schedule (self, updated); } static GcalRecurrenceLimitType @@ -787,8 +787,8 @@ on_repeat_duration_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GcalRecurrenceLimitType limit_type = get_recurence_limit_type (self); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recur_limit_type (self->values, limit_type); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_recur_limit_type (self->values, limit_type); + update_from_event_schedule (self, updated); } static GcalRecurrenceFrequency @@ -807,8 +807,8 @@ on_repeat_type_changed_cb (GtkWidget *widget, GcalScheduleSection *self) { GcalRecurrenceFrequency frequency = get_recurrence_frequency (self); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_recur_frequency (self->values, frequency); - update_from_section_values (self, updated); + GcalEventSchedule *updated = gcal_event_schedule_set_recur_frequency (self->values, frequency); + update_from_event_schedule (self, updated); } static void @@ -821,9 +821,9 @@ on_time_format_changed_cb (GcalScheduleSection *self) */ GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - GcalScheduleSectionValues *updated = gcal_schedule_section_values_set_time_format (self->values, time_format); + GcalEventSchedule *updated = gcal_event_schedule_set_time_format (self->values, time_format); - update_from_section_values (self, updated); + update_from_event_schedule (self, updated); } } @@ -859,7 +859,7 @@ gcal_schedule_section_set_event (GcalEventEditorSection *section, self->flags = flags; time_format = gcal_context_get_time_format (self->context); - self->values = gcal_schedule_section_values_from_event (event, time_format); + self->values = gcal_event_schedule_from_event (event, time_format); if (!event) GCAL_RETURN (); @@ -909,29 +909,29 @@ gcal_schedule_section_apply (GcalEventEditorSection *section) gcal_schedule_section_apply_to_event (self, self->event); - gcal_schedule_section_values_free (self->values); + gcal_event_schedule_free (self->values); GcalTimeFormat time_format = gcal_context_get_time_format (self->context); - self->values = gcal_schedule_section_values_from_event (self->event, time_format); + self->values = gcal_event_schedule_from_event (self->event, time_format); GCAL_EXIT; } static gboolean -recurrence_changed (const GcalScheduleSectionValues *values) +recurrence_changed (const GcalEventSchedule *values) { return !gcal_recurrence_is_equal (values->orig.recur, values->curr.recur); } static gboolean -day_changed (const GcalScheduleSectionValues *values) +day_changed (const GcalEventSchedule *values) { return (gcal_date_time_compare_date (values->curr.date_start, values->orig.date_start) < 0 || gcal_date_time_compare_date (values->curr.date_end, values->orig.date_end) > 0); } static gboolean -values_changed (const GcalScheduleSectionValues *values) +values_changed (const GcalEventSchedule *values) { const GcalScheduleValues *orig = &values->orig; const GcalScheduleValues *curr = &values->curr; @@ -988,7 +988,7 @@ gcal_schedule_section_finalize (GObject *object) { GcalScheduleSection *self = (GcalScheduleSection *)object; - g_clear_pointer (&self->values, gcal_schedule_section_values_free); + g_clear_pointer (&self->values, gcal_event_schedule_free); g_clear_object (&self->context); g_clear_object (&self->event); @@ -1107,12 +1107,12 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) /** * Extracts the values from @event that are needed to populate #GcalScheduleSection. * - * Returns: a #GcalScheduleSectionValues ready for use. Free it with - * gcal_schedule_section_values_free(). + * Returns: a #GcalEventSchedule ready for use. Free it with + * gcal_event_schedule_free(). */ -GcalScheduleSectionValues * -gcal_schedule_section_values_from_event (GcalEvent *event, - GcalTimeFormat time_format) +GcalEventSchedule * +gcal_event_schedule_from_event (GcalEvent *event, + GcalTimeFormat time_format) { GcalScheduleValues values; memset (&values, 0, sizeof (values)); @@ -1135,9 +1135,9 @@ gcal_schedule_section_values_from_event (GcalEvent *event, } } - GcalScheduleSectionValues *section_values = g_new0 (GcalScheduleSectionValues, 1); + GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); - *section_values = (GcalScheduleSectionValues) { + *section_values = (GcalEventSchedule) { .orig = gcal_schedule_values_copy (&values), .curr = values, .time_format = time_format, @@ -1170,14 +1170,14 @@ values_with_date_times (const char *start, const char *end, gboolean all_day) return values; } -static GcalScheduleSectionValues * -section_values_with_date_times (const char *start, const char *end, gboolean all_day) +static GcalEventSchedule * +event_schedule_with_date_times (const char *start, const char *end, gboolean all_day) { GcalScheduleValues values = values_with_date_times (start, end, all_day); - GcalScheduleSectionValues *section_values = g_new0 (GcalScheduleSectionValues, 1); + GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); - *section_values = (GcalScheduleSectionValues) { + *section_values = (GcalEventSchedule) { .orig = gcal_schedule_values_copy (&values), .curr = values, .time_format = GCAL_TIME_FORMAT_24H, @@ -1197,13 +1197,13 @@ test_setting_start_date_after_end_date_resets_end_date (void) * * We want to test that end becomes 12:00 as well. */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); - g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_start_date (values, two_hours_later); + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date (values, two_hours_later); g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_later)); g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_later)); } @@ -1219,13 +1219,13 @@ test_setting_end_date_before_start_date_resets_start_date (void) * * We want to test that start becomes 09:00 as well. */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); - g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date (values, two_hours_earlier); + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date (values, two_hours_earlier); g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_earlier)); g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_earlier)); } @@ -1243,15 +1243,15 @@ test_setting_start_datetime_preserves_end_timezone (void) * * (imagine driving for one hour while crossing timezones) */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); g_autoptr (GDateTime) new_start = g_date_time_new_from_iso8601 ("20250303T11:30:00-06:00", NULL); g_autoptr (GDateTime) expected_end = g_date_time_new_from_iso8601 ("20250303T13:30:00-05:00", NULL); - g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_start_date_time (values, new_start); + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date_time (values, new_start); g_assert (g_date_time_equal (new_values->curr.date_start, new_start)); g_assert (g_date_time_equal (new_values->curr.date_end, expected_end)); @@ -1270,15 +1270,15 @@ test_setting_end_datetime_preserves_start_timezone (void) * * (imagine driving for one hour while crossing timezones) */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); g_autoptr (GDateTime) new_end = g_date_time_new_from_iso8601 ("20250303T09:30:00-05:00", NULL); g_autoptr (GDateTime) expected_start = g_date_time_new_from_iso8601 ("20250303T07:30:00-06:00", NULL); - g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date_time (values, new_end); + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date_time (values, new_end); g_assert (g_date_time_equal (new_values->curr.date_end, new_end)); g_assert (g_date_time_equal (new_values->curr.date_start, expected_start)); @@ -1301,9 +1301,9 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) * Check that the values gets back end = (start + 2 days) */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times ("20250303T00:00:00-06:00", - "20250304T00:00:00-06:00", - TRUE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times ("20250303T00:00:00-06:00", + "20250304T00:00:00-06:00", + TRUE); /* Compute the widget state from the values; check the displayed dates (should be the same) */ @@ -1325,7 +1325,7 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) g_autoptr (GDateTime) displayed_end_date_plus_one_day = g_date_time_new_from_iso8601 ("20250304T00:00:00-06:00", NULL); g_assert (displayed_end_date_plus_one_day != NULL); - g_autoptr (GcalScheduleSectionValues) new_values = gcal_schedule_section_values_set_end_date (values, displayed_end_date_plus_one_day); + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date (values, displayed_end_date_plus_one_day); /* Check the real iCalendar value for the new date, should be different from the displayed value */ @@ -1353,13 +1353,13 @@ test_43_switching_to_all_day_preserves_timezones (void) * original times. */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times ("20250303T12:00:00-06:00", - "20250304T13:00:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times ("20250303T12:00:00-06:00", + "20250304T13:00:00-06:00", + FALSE); /* set all-day and check that this modified the times to midnight */ - g_autoptr (GcalScheduleSectionValues) all_day_values = gcal_schedule_section_values_set_all_day (values, TRUE); + g_autoptr (GcalEventSchedule) all_day_values = gcal_event_schedule_set_all_day (values, TRUE); g_autoptr (GDateTime) expected_start_date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); g_autoptr (GDateTime) expected_end_date = g_date_time_new_from_iso8601 ("20250305T00:00:00-06:00", NULL); @@ -1369,7 +1369,7 @@ test_43_switching_to_all_day_preserves_timezones (void) /* turn off all-day; check that times were restored */ - g_autoptr (GcalScheduleSectionValues) not_all_day_values = gcal_schedule_section_values_set_all_day (all_day_values, FALSE); + g_autoptr (GcalEventSchedule) not_all_day_values = gcal_event_schedule_set_all_day (all_day_values, FALSE); g_autoptr (GDateTime) expected_start_date2 = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); g_autoptr (GDateTime) expected_end_date2 = g_date_time_new_from_iso8601 ("20250304T13:00:00-06:00", NULL); @@ -1416,17 +1416,17 @@ static void test_turning_on_recurrence_count_turns_on_its_widget (void) { /* Start with some configuration */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250411T10:00:00-06:00", - "20250411T11:30:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); /* Turn on recurrence */ - g_autoptr (GcalScheduleSectionValues) with_recur = - gcal_schedule_section_values_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); + g_autoptr (GcalEventSchedule) with_recur = + gcal_event_schedule_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); /* Set it to "count" */ - g_autoptr (GcalScheduleSectionValues) with_count = - gcal_schedule_section_values_set_recur_limit_type (with_recur, GCAL_RECURRENCE_COUNT); + g_autoptr (GcalEventSchedule) with_count = + gcal_event_schedule_set_recur_limit_type (with_recur, GCAL_RECURRENCE_COUNT); g_autoptr (WidgetState) state = widget_state_from_values (with_count); g_assert_true (state->recurrence.duration_combo_visible); @@ -1438,17 +1438,17 @@ static void test_turning_on_recurrence_until_turns_on_its_widget (void) { /* Start with some configuration */ - g_autoptr (GcalScheduleSectionValues) values = section_values_with_date_times("20250411T10:00:00-06:00", - "20250411T11:30:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); /* Turn on recurrence */ - g_autoptr (GcalScheduleSectionValues) with_recur = - gcal_schedule_section_values_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); + g_autoptr (GcalEventSchedule) with_recur = + gcal_event_schedule_set_recur_frequency (values, GCAL_RECURRENCE_DAILY); /* Set it to "count" */ - g_autoptr (GcalScheduleSectionValues) with_until = - gcal_schedule_section_values_set_recur_limit_type (with_recur, GCAL_RECURRENCE_UNTIL); + g_autoptr (GcalEventSchedule) with_until = + gcal_event_schedule_set_recur_limit_type (with_recur, GCAL_RECURRENCE_UNTIL); g_autoptr (WidgetState) state = widget_state_from_values (with_until); g_assert_true (state->recurrence.duration_combo_visible); diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index 3181a4faf..eebea5920 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -57,17 +57,17 @@ typedef struct /* copied from GcalContext to avoid a dependency on it */ GcalTimeFormat time_format; -} GcalScheduleSectionValues; +} GcalEventSchedule; gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self); gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self); -GcalScheduleSectionValues *gcal_schedule_section_values_from_event (GcalEvent *event, - GcalTimeFormat time_format); -void gcal_schedule_section_values_free (GcalScheduleSectionValues *values); +GcalEventSchedule *gcal_event_schedule_from_event (GcalEvent *event, + GcalTimeFormat time_format); +void gcal_event_schedule_free (GcalEventSchedule *values); -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalScheduleSectionValues, gcal_schedule_section_values_free); +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalEventSchedule, gcal_event_schedule_free); /* Tests */ -- GitLab From 309a91ddcb93dcc533a370e2c134e574d5ed4a4b Mon Sep 17 00:00:00 2001 From: Federico Mena Quintero Date: Fri, 11 Apr 2025 19:11:27 -0600 Subject: [PATCH 63/63] Move GcalEventSchedule to new files gcal-event-schedule.[ch] I am not sure that these should be in src/core, since the gcal_event_schedule_set_*() functions do have a bunch of GUI policy. Part-of: --- src/gui/event-editor/gcal-event-schedule.c | 654 +++++++++++++++++++ src/gui/event-editor/gcal-event-schedule.h | 94 +++ src/gui/event-editor/gcal-schedule-section.c | 639 +----------------- src/gui/event-editor/gcal-schedule-section.h | 32 - src/gui/event-editor/meson.build | 1 + src/gui/gcal-tests.c | 2 + 6 files changed, 761 insertions(+), 661 deletions(-) create mode 100644 src/gui/event-editor/gcal-event-schedule.c create mode 100644 src/gui/event-editor/gcal-event-schedule.h diff --git a/src/gui/event-editor/gcal-event-schedule.c b/src/gui/event-editor/gcal-event-schedule.c new file mode 100644 index 000000000..ed4694dce --- /dev/null +++ b/src/gui/event-editor/gcal-event-schedule.c @@ -0,0 +1,654 @@ +/* gcal-event-schedule.c - immutable struct for the date and recurrence values of an event + * + * Copyright 2025 Federico Mena Quintero + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalEventSchedule" + +#include "gcal-date-time-utils.h" +#include "gcal-debug.h" +#include "gcal-event.h" +#include "gcal-recurrence.h" +#include "gcal-event-schedule.h" + +GcalScheduleValues +gcal_schedule_values_copy (const GcalScheduleValues *values) +{ + GcalScheduleValues copy; + + copy.all_day = values->all_day; + copy.date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; + copy.date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; + copy.recur = values->recur ? gcal_recurrence_copy (values->recur) : NULL; + + return copy; +} + +static GcalEventSchedule * +gcal_event_schedule_copy (const GcalEventSchedule *values) +{ + GcalEventSchedule *copy = g_new0 (GcalEventSchedule, 1); + + copy->orig = gcal_schedule_values_copy (&values->orig); + copy->curr = gcal_schedule_values_copy (&values->curr); + copy->time_format = values->time_format; + + return copy; +} + +static void +gcal_schedule_values_free (GcalScheduleValues *values) +{ + values->all_day = FALSE; + + g_clear_pointer (&values->date_start, g_date_time_unref); + g_clear_pointer (&values->date_end, g_date_time_unref); + g_clear_pointer (&values->recur, gcal_recurrence_unref); +} + +/** + * gcal_event_schedule_free(): + * + * Frees the contents of @values. + */ +void +gcal_event_schedule_free (GcalEventSchedule *values) +{ + gcal_schedule_values_free (&values->orig); + gcal_schedule_values_free (&values->curr); + g_free (values); +} + +GcalEventSchedule* +gcal_event_schedule_set_all_day (const GcalEventSchedule *values, gboolean all_day) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + if (all_day == values->curr.all_day) + { + return copy; + } + + if (all_day) + { + /* We are switching from a time-slot event to an all-day one. If we had + * + * date_start = $day1 + $time + * date_end = $day2 + $time + * + * we want to switch to + * + * date_start = $day1 + 00:00 (midnight) + * date_end = $day2 + 1 day + 00:00 (midnight of the next day) + */ + + GDateTime *start = values->curr.date_start; + GDateTime *end = values->curr.date_end; + + g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), + g_date_time_get_year (start), + g_date_time_get_month (start), + g_date_time_get_day_of_month (start), + 0, + 0, + 0.0); + + g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), + g_date_time_get_year (end), + g_date_time_get_month (end), + g_date_time_get_day_of_month (end), + 0, + 0, + 0.0); + g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, 1); + + gcal_set_date_time (©->curr.date_start, new_start); + gcal_set_date_time (©->curr.date_end, new_end); + } + else + { + /* We are switching from an all-day event to a time-slot one. In this case, + * we want to preserve the current dates, but restore the times of the original + * event. + */ + + GDateTime *start = values->curr.date_start; + GDateTime *end = values->curr.date_end; + + g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), + g_date_time_get_year (start), + g_date_time_get_month (start), + g_date_time_get_day_of_month (start), + g_date_time_get_hour (values->orig.date_start), + g_date_time_get_minute (values->orig.date_start), + g_date_time_get_seconds (values->orig.date_start)); + + g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), + g_date_time_get_year (end), + g_date_time_get_month (end), + g_date_time_get_day_of_month (end), + g_date_time_get_hour (values->orig.date_end), + g_date_time_get_minute (values->orig.date_end), + g_date_time_get_seconds (values->orig.date_end)); + + g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, -1); + + gcal_set_date_time (©->curr.date_start, new_start); + gcal_set_date_time (©->curr.date_end, new_end); + } + + copy->curr.all_day = all_day; + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_time_format (const GcalEventSchedule *values, GcalTimeFormat time_format) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + copy->time_format = time_format; + return copy; +} + +/* The start_date_row widget has changed. We need to sync it to the values and + * adjust the other values based on it. + */ +GcalEventSchedule * +gcal_event_schedule_set_start_date (const GcalEventSchedule *values, GDateTime *start) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + gcal_set_date_time (©->curr.date_start, start); + + if (g_date_time_compare (start, copy->curr.date_end) == 1) + { + gcal_set_date_time (©->curr.date_end, start); + } + + return copy; +} + +/* The end_date_row widget has changed. We need to sync it to the values and + * adjust the other values based on it. + */ +GcalEventSchedule * +gcal_event_schedule_set_end_date (const GcalEventSchedule *values, GDateTime *end) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + if (copy->curr.all_day) + { + /* See the comment "While in iCalendar..." in widget_state_from_values(). Here we + * take the human-readable end-date, and turn it into the appropriate value for + * iCalendar. + */ + g_autoptr (GDateTime) fake_end_date = g_date_time_add_days (end, 1); + gcal_set_date_time (©->curr.date_end, fake_end_date); + } + else + { + gcal_set_date_time (©->curr.date_end, end); + } + + if (g_date_time_compare (copy->curr.date_start, end) == 1) + { + gcal_set_date_time (©->curr.date_start, end); + } + + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_start_date_time (const GcalEventSchedule *values, GDateTime *start) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + gcal_set_date_time (©->curr.date_start, start); + + if (g_date_time_compare (start, copy->curr.date_end) == 1) + { + GTimeZone *end_tz = g_date_time_get_timezone (copy->curr.date_end); + g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); + g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); + + gcal_set_date_time (©->curr.date_end, new_end_in_end_tz); + } + + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_end_date_time (const GcalEventSchedule *values, GDateTime *end) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + gcal_set_date_time (©->curr.date_end, end); + + if (g_date_time_compare (copy->curr.date_start, end) == 1) + { + GTimeZone *start_tz = g_date_time_get_timezone (copy->curr.date_start); + g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); + g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); + + gcal_set_date_time (©->curr.date_start, new_start_in_start_tz); + } + + return copy; +} + +static GcalRecurrence * +recur_change_frequency (GcalRecurrence *old_recur, GcalRecurrenceFrequency frequency) +{ + if (frequency == GCAL_RECURRENCE_NO_REPEAT) + { + /* Invariant: GCAL_RECURRENCE_NO_REPEAT is reduced to a NULL recurrence */ + return NULL; + } + else + { + GcalRecurrence *new_recur; + + if (old_recur) + { + new_recur = gcal_recurrence_copy (old_recur); + } + else + { + new_recur = gcal_recurrence_new (); + } + + new_recur->frequency = frequency; + + return new_recur; + } +} + +static GcalRecurrence * +recur_change_limit_type (GcalRecurrence *old_recur, GcalRecurrenceLimitType limit_type, GDateTime *date_start) +{ + /* An old recurrence would already be present with something other than GCAL_RECURRENCE_NO_REPEAT */ + g_assert (old_recur != NULL); + g_assert (old_recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + + GcalRecurrence *new_recur = gcal_recurrence_copy (old_recur); + + if (limit_type == old_recur->limit_type) + { + return new_recur; + } + else + { + new_recur->limit_type = limit_type; + + switch (limit_type) + { + case GCAL_RECURRENCE_FOREVER: + break; + + case GCAL_RECURRENCE_COUNT: + /* Other policies are possible. This is just "more than once". */ + new_recur->limit.count = 2; + break; + + case GCAL_RECURRENCE_UNTIL: + /* Again, other policies are possible. For now, leave the decision to the user. */ + g_assert (date_start != NULL); + new_recur->limit.until = g_date_time_ref (date_start); + break; + + default: + g_assert_not_reached (); + } + } + + return new_recur; +} + +GcalEventSchedule * +gcal_event_schedule_set_recur_frequency (const GcalEventSchedule *values, + GcalRecurrenceFrequency frequency) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + GcalRecurrence *new_recur = recur_change_frequency (copy->curr.recur, frequency); + g_clear_pointer (©->curr.recur, gcal_recurrence_unref); + copy->curr.recur = new_recur; + + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_recur_limit_type (const GcalEventSchedule *values, + GcalRecurrenceLimitType limit_type) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + GcalRecurrence *new_recur = recur_change_limit_type (copy->curr.recur, limit_type, values->curr.date_start); + g_clear_pointer (©->curr.recur, gcal_recurrence_unref); + copy->curr.recur = new_recur; + + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_recurrence_count (const GcalEventSchedule *values, + guint count) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + /* An old recurrence would already be present */ + g_assert (copy->curr.recur != NULL); + g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_COUNT); + + copy->curr.recur->limit.count = count; + + return copy; +} + +GcalEventSchedule * +gcal_event_schedule_set_recurrence_until (const GcalEventSchedule *values, + GDateTime *until) +{ + GcalEventSchedule *copy = gcal_event_schedule_copy (values); + + /* An old recurrence would already be present */ + g_assert (copy->curr.recur != NULL); + g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); + g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_UNTIL); + + gcal_set_date_time (©->curr.recur->limit.until, until); + + return copy; +} + +/** + * Extracts the values from @event that are needed to populate #GcalScheduleSection. + * + * Returns: a #GcalEventSchedule ready for use. Free it with + * gcal_event_schedule_free(). + */ +GcalEventSchedule * +gcal_event_schedule_from_event (GcalEvent *event, + GcalTimeFormat time_format) +{ + GcalScheduleValues values; + memset (&values, 0, sizeof (values)); + + if (event) + { + GcalRecurrence *recur = gcal_event_get_recurrence (event); + + values.all_day = gcal_event_get_all_day (event); + values.date_start = g_date_time_ref (gcal_event_get_date_start (event)); + values.date_end = g_date_time_ref (gcal_event_get_date_end (event)); + + if (recur) + { + values.recur = gcal_recurrence_copy (recur); + } + else + { + values.recur = NULL; + } + } + + GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); + + *section_values = (GcalEventSchedule) { + .orig = gcal_schedule_values_copy (&values), + .curr = values, + .time_format = time_format, + }; + + return section_values; +} + +/* Builds a new GcalscheduleSectgionValues from two ISO 8601 date-times; be sure to + * include your timezone if you need it. This function is just to be used from tests. + **/ +static GcalScheduleValues +values_with_date_times (const char *start, const char *end, gboolean all_day) +{ + g_autoptr (GDateTime) start_date = g_date_time_new_from_iso8601 (start, NULL); + g_assert (start_date != NULL); + + g_autoptr (GDateTime) end_date = g_date_time_new_from_iso8601 (end, NULL); + g_assert (end_date != NULL); + + g_assert (g_date_time_compare (start_date, end_date) == -1); + + GcalScheduleValues values = { + .all_day = all_day, + .date_start = g_date_time_ref (start_date), + .date_end = g_date_time_ref (end_date), + .recur = NULL, + }; + + return values; +} + +/* This is just for writing tests */ +GcalEventSchedule * +gcal_event_schedule_with_date_times (const char *start, const char *end, gboolean all_day) +{ + GcalScheduleValues values = values_with_date_times (start, end, all_day); + + GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); + + *section_values = (GcalEventSchedule) { + .orig = gcal_schedule_values_copy (&values), + .curr = values, + .time_format = GCAL_TIME_FORMAT_24H, + }; + + return section_values; +} + + +static void +test_setting_start_date_after_end_date_resets_end_date (void) +{ + /* We start with + * start = 10:00 + * end = 11:00 + * + * Then we set start to 12:00 + * + * We want to test that end becomes 12:00 as well. + */ + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); + + g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); + + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date (values, two_hours_later); + g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_later)); + g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_later)); +} + +static void +test_setting_end_date_before_start_date_resets_start_date (void) +{ + /* We start with + * start = 10:00 + * end = 11:00 + * + * Then we set end to 09:00 + * + * We want to test that start becomes 09:00 as well. + */ + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:00:00-06:00", + FALSE); + + g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); + + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date (values, two_hours_earlier); + g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_earlier)); + g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_earlier)); +} + +static void +test_setting_start_datetime_preserves_end_timezone (void) +{ + /* Start with + * start = 10:00, UTC-6 + * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) + * + * Set the start date to 11:30, UTC-6 + * + * End date should be 13:30, UTC-5 (i.e. end_date was set to the same as start_date, but in a different tz) + * + * (imagine driving for one hour while crossing timezones) + */ + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); + g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); + + g_autoptr (GDateTime) new_start = g_date_time_new_from_iso8601 ("20250303T11:30:00-06:00", NULL); + g_autoptr (GDateTime) expected_end = g_date_time_new_from_iso8601 ("20250303T13:30:00-05:00", NULL); + + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date_time (values, new_start); + g_assert (g_date_time_equal (new_values->curr.date_start, new_start)); + + g_assert (g_date_time_equal (new_values->curr.date_end, expected_end)); +} + +static void +test_setting_end_datetime_preserves_start_timezone (void) +{ + /* Start with + * start = 10:00, UTC-6 + * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) + * + * Set the end date to 09:30, UTC-5 + * + * Start date should be 07:30, UTC-6 (set to one hour earlier than the end time, with the original start's tz) + * + * (imagine driving for one hour while crossing timezones) + */ + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250303T10:00:00-06:00", + "20250303T11:30:00-05:00", + FALSE); + g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); + + g_autoptr (GDateTime) new_end = g_date_time_new_from_iso8601 ("20250303T09:30:00-05:00", NULL); + g_autoptr (GDateTime) expected_start = g_date_time_new_from_iso8601 ("20250303T07:30:00-06:00", NULL); + + g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date_time (values, new_end); + g_assert (g_date_time_equal (new_values->curr.date_end, new_end)); + + g_assert (g_date_time_equal (new_values->curr.date_start, expected_start)); +} + +static void +test_43_switching_to_all_day_preserves_timezones (void) +{ + g_test_bug ("43"); + /* https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/43 + * + * Given an event that is not all-day, e.g. from $day1-12:00 to $day2-13:00, if one turns it to an + * all-day event, we want to switch the dates like this: + * + * start: $day1's 00:00 + * end: $day2's 24:00 (e.g. ($day2 + 1)'s 00:00) + * + * Also test that after that, changing the dates and turning off all-day restores the + * original times. + */ + + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times ("20250303T12:00:00-06:00", + "20250304T13:00:00-06:00", + FALSE); + + /* set all-day and check that this modified the times to midnight */ + + g_autoptr (GcalEventSchedule) all_day_values = gcal_event_schedule_set_all_day (values, TRUE); + + g_autoptr (GDateTime) expected_start_date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); + g_autoptr (GDateTime) expected_end_date = g_date_time_new_from_iso8601 ("20250305T00:00:00-06:00", NULL); + + g_assert (g_date_time_equal (all_day_values->curr.date_start, expected_start_date)); + g_assert (g_date_time_equal (all_day_values->curr.date_end, expected_end_date)); + + /* turn off all-day; check that times were restored */ + + g_autoptr (GcalEventSchedule) not_all_day_values = gcal_event_schedule_set_all_day (all_day_values, FALSE); + + g_autoptr (GDateTime) expected_start_date2 = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); + g_autoptr (GDateTime) expected_end_date2 = g_date_time_new_from_iso8601 ("20250304T13:00:00-06:00", NULL); + + g_assert (g_date_time_equal (not_all_day_values->curr.date_start, expected_start_date2)); + g_assert (g_date_time_equal (not_all_day_values->curr.date_end, expected_end_date2)); +} + +static void +test_recur_changes_to_no_repeat (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GcalRecurrence) new_recur = recur_change_frequency (old_recur, GCAL_RECURRENCE_NO_REPEAT); + g_assert_null (new_recur); +} + +static void +test_recur_changes_limit_type_to_count (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_COUNT, NULL); + g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_COUNT); + g_assert_cmpint (new_recur->limit.count, ==, 2); +} + +static void +test_recur_changes_limit_type_to_until (void) +{ + g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); + old_recur->frequency = GCAL_RECURRENCE_WEEKLY; + + g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250411T00:00:00-06:00", NULL); + + g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_UNTIL, date); + g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_UNTIL); + g_assert (g_date_time_equal (new_recur->limit.until, date)); +} + +void +gcal_event_schedule_add_tests (void) +{ + g_test_add_func ("/event_editor/event_schedule/setting_start_date_after_end_date_resets_end_date", + test_setting_start_date_after_end_date_resets_end_date); + g_test_add_func ("/event_editor/event_schedule/setting_end_date_before_start_date_resets_start_date", + test_setting_end_date_before_start_date_resets_start_date); + g_test_add_func ("/event_editor/event_schedule/setting_start_datetime_preserves_end_timezone", + test_setting_start_datetime_preserves_end_timezone); + g_test_add_func ("/event_editor/event_schedule/setting_end_datetime_preserves_start_timezone", + test_setting_end_datetime_preserves_start_timezone); + g_test_add_func ("/event_editor/event_schedule/43_switching_to_all_day_preserves_timezones", + test_43_switching_to_all_day_preserves_timezones); + g_test_add_func ("/event_editor/event_schedule/recur_changes_to_no_repeat", + test_recur_changes_to_no_repeat); + g_test_add_func ("/event_editor/event_schedule/recur_changes_limit_type_to_count", + test_recur_changes_limit_type_to_count); + g_test_add_func ("/event_editor/event_schedule/recur_changes_limit_type_to_until", + test_recur_changes_limit_type_to_until); +} diff --git a/src/gui/event-editor/gcal-event-schedule.h b/src/gui/event-editor/gcal-event-schedule.h new file mode 100644 index 000000000..a7a83c520 --- /dev/null +++ b/src/gui/event-editor/gcal-event-schedule.h @@ -0,0 +1,94 @@ +/* gcal-event-schedule.h - immutable struct for the date and recurrence values of an event + * + * Copyright 2025 Federico Mena Quintero + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gcal-enums.h" +#include "gcal-event.h" +#include "gcal-recurrence.h" + +G_BEGIN_DECLS + +/* Values from a GcalEvent that this widget can manipulate. + * + * We keep an immutable copy of the original event's values, and later generate + * a new GcalScheduleValues with the updated data from the widgetry. + */ +typedef struct +{ + gboolean all_day; + + GDateTime *date_start; + GDateTime *date_end; + + GcalRecurrence *recur; +} GcalScheduleValues; + +typedef struct +{ + /* original values from event */ + GcalScheduleValues orig; + + /* current values, as modified by actions from widgets */ + GcalScheduleValues curr; + + /* copied from GcalContext to avoid a dependency on it */ + GcalTimeFormat time_format; +} GcalEventSchedule; + +GcalScheduleValues gcal_schedule_values_copy (const GcalScheduleValues *values); + + +GcalEventSchedule *gcal_event_schedule_from_event (GcalEvent *event, + GcalTimeFormat time_format); +void gcal_event_schedule_free (GcalEventSchedule *values); + +GcalEventSchedule *gcal_event_schedule_set_all_day (const GcalEventSchedule *values, + gboolean all_day); +GcalEventSchedule *gcal_event_schedule_set_time_format (const GcalEventSchedule *values, + GcalTimeFormat time_format); +GcalEventSchedule *gcal_event_schedule_set_start_date (const GcalEventSchedule *values, + GDateTime *start); +GcalEventSchedule *gcal_event_schedule_set_end_date (const GcalEventSchedule *values, + GDateTime *end); +GcalEventSchedule *gcal_event_schedule_set_start_date_time (const GcalEventSchedule *values, + GDateTime *start); +GcalEventSchedule *gcal_event_schedule_set_end_date_time (const GcalEventSchedule *values, + GDateTime *end); +GcalEventSchedule *gcal_event_schedule_set_recur_frequency (const GcalEventSchedule *values, + GcalRecurrenceFrequency frequency); +GcalEventSchedule *gcal_event_schedule_set_recur_limit_type (const GcalEventSchedule *values, + GcalRecurrenceLimitType limit_type); +GcalEventSchedule *gcal_event_schedule_set_recurrence_count (const GcalEventSchedule *values, + guint count); +GcalEventSchedule *gcal_event_schedule_set_recurrence_until (const GcalEventSchedule *values, + GDateTime *until); + +GcalEventSchedule *gcal_event_schedule_with_date_times (const char *start, + const char *end, + gboolean all_day); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalEventSchedule, gcal_event_schedule_free); + +/* Tests */ + +void gcal_event_schedule_add_tests (void); + +G_END_DECLS diff --git a/src/gui/event-editor/gcal-schedule-section.c b/src/gui/event-editor/gcal-schedule-section.c index 6dc7ba632..b9bea1d10 100644 --- a/src/gui/event-editor/gcal-schedule-section.c +++ b/src/gui/event-editor/gcal-schedule-section.c @@ -27,6 +27,7 @@ #include "gcal-debug.h" #include "gcal-event.h" #include "gcal-event-editor-section.h" +#include "gcal-event-schedule.h" #include "gcal-recurrence.h" #include "gcal-schedule-section.h" #include "gcal-utils.h" @@ -127,355 +128,6 @@ static void on_schedule_type_changed_cb (GtkWidget static void on_number_of_occurrences_changed_cb (GcalScheduleSection *self); static void on_until_date_changed_cb (GcalScheduleSection *self); -static GcalScheduleValues -gcal_schedule_values_copy (const GcalScheduleValues *values) -{ - GcalScheduleValues copy; - - copy.all_day = values->all_day; - copy.date_start = values->date_start ? g_date_time_ref (values->date_start) : NULL; - copy.date_end = values->date_end ? g_date_time_ref (values->date_end) : NULL; - copy.recur = values->recur ? gcal_recurrence_copy (values->recur) : NULL; - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_copy (const GcalEventSchedule *values) -{ - GcalEventSchedule *copy = g_new0 (GcalEventSchedule, 1); - - copy->orig = gcal_schedule_values_copy (&values->orig); - copy->curr = gcal_schedule_values_copy (&values->curr); - copy->time_format = values->time_format; - - return copy; -} - -static void -gcal_schedule_values_free (GcalScheduleValues *values) -{ - values->all_day = FALSE; - - g_clear_pointer (&values->date_start, g_date_time_unref); - g_clear_pointer (&values->date_end, g_date_time_unref); - g_clear_pointer (&values->recur, gcal_recurrence_unref); -} - -/** - * gcal_event_schedule_free(): - * - * Frees the contents of @values. - */ -void -gcal_event_schedule_free (GcalEventSchedule *values) -{ - gcal_schedule_values_free (&values->orig); - gcal_schedule_values_free (&values->curr); - g_free (values); -} - -static GcalEventSchedule* -gcal_event_schedule_set_all_day (const GcalEventSchedule *values, gboolean all_day) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - if (all_day == values->curr.all_day) - { - return copy; - } - - if (all_day) - { - /* We are switching from a time-slot event to an all-day one. If we had - * - * date_start = $day1 + $time - * date_end = $day2 + $time - * - * we want to switch to - * - * date_start = $day1 + 00:00 (midnight) - * date_end = $day2 + 1 day + 00:00 (midnight of the next day) - */ - - GDateTime *start = values->curr.date_start; - GDateTime *end = values->curr.date_end; - - g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), - g_date_time_get_year (start), - g_date_time_get_month (start), - g_date_time_get_day_of_month (start), - 0, - 0, - 0.0); - - g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), - g_date_time_get_year (end), - g_date_time_get_month (end), - g_date_time_get_day_of_month (end), - 0, - 0, - 0.0); - g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, 1); - - gcal_set_date_time (©->curr.date_start, new_start); - gcal_set_date_time (©->curr.date_end, new_end); - } - else - { - /* We are switching from an all-day event to a time-slot one. In this case, - * we want to preserve the current dates, but restore the times of the original - * event. - */ - - GDateTime *start = values->curr.date_start; - GDateTime *end = values->curr.date_end; - - g_autoptr (GDateTime) new_start = g_date_time_new (g_date_time_get_timezone (start), - g_date_time_get_year (start), - g_date_time_get_month (start), - g_date_time_get_day_of_month (start), - g_date_time_get_hour (values->orig.date_start), - g_date_time_get_minute (values->orig.date_start), - g_date_time_get_seconds (values->orig.date_start)); - - g_autoptr (GDateTime) tmp = g_date_time_new (g_date_time_get_timezone (end), - g_date_time_get_year (end), - g_date_time_get_month (end), - g_date_time_get_day_of_month (end), - g_date_time_get_hour (values->orig.date_end), - g_date_time_get_minute (values->orig.date_end), - g_date_time_get_seconds (values->orig.date_end)); - - g_autoptr (GDateTime) new_end = g_date_time_add_days (tmp, -1); - - gcal_set_date_time (©->curr.date_start, new_start); - gcal_set_date_time (©->curr.date_end, new_end); - } - - copy->curr.all_day = all_day; - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_time_format (const GcalEventSchedule *values, GcalTimeFormat time_format) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - copy->time_format = time_format; - return copy; -} - -/* The start_date_row widget has changed. We need to sync it to the values and - * adjust the other values based on it. - */ -static GcalEventSchedule * -gcal_event_schedule_set_start_date (const GcalEventSchedule *values, GDateTime *start) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - gcal_set_date_time (©->curr.date_start, start); - - if (g_date_time_compare (start, copy->curr.date_end) == 1) - { - gcal_set_date_time (©->curr.date_end, start); - } - - return copy; -} - -/* The end_date_row widget has changed. We need to sync it to the values and - * adjust the other values based on it. - */ -static GcalEventSchedule * -gcal_event_schedule_set_end_date (const GcalEventSchedule *values, GDateTime *end) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - if (copy->curr.all_day) - { - /* See the comment "While in iCalendar..." in widget_state_from_values(). Here we - * take the human-readable end-date, and turn it into the appropriate value for - * iCalendar. - */ - g_autoptr (GDateTime) fake_end_date = g_date_time_add_days (end, 1); - gcal_set_date_time (©->curr.date_end, fake_end_date); - } - else - { - gcal_set_date_time (©->curr.date_end, end); - } - - if (g_date_time_compare (copy->curr.date_start, end) == 1) - { - gcal_set_date_time (©->curr.date_start, end); - } - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_start_date_time (const GcalEventSchedule *values, GDateTime *start) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - gcal_set_date_time (©->curr.date_start, start); - - if (g_date_time_compare (start, copy->curr.date_end) == 1) - { - GTimeZone *end_tz = g_date_time_get_timezone (copy->curr.date_end); - g_autoptr (GDateTime) new_end = g_date_time_add_hours (start, 1); - g_autoptr (GDateTime) new_end_in_end_tz = g_date_time_to_timezone (new_end, end_tz); - - gcal_set_date_time (©->curr.date_end, new_end_in_end_tz); - } - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_end_date_time (const GcalEventSchedule *values, GDateTime *end) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - gcal_set_date_time (©->curr.date_end, end); - - if (g_date_time_compare (copy->curr.date_start, end) == 1) - { - GTimeZone *start_tz = g_date_time_get_timezone (copy->curr.date_start); - g_autoptr (GDateTime) new_start = g_date_time_add_hours (end, -1); - g_autoptr (GDateTime) new_start_in_start_tz = g_date_time_to_timezone (new_start, start_tz); - - gcal_set_date_time (©->curr.date_start, new_start_in_start_tz); - } - - return copy; -} - -static GcalRecurrence * -recur_change_frequency (GcalRecurrence *old_recur, GcalRecurrenceFrequency frequency) -{ - if (frequency == GCAL_RECURRENCE_NO_REPEAT) - { - /* Invariant: GCAL_RECURRENCE_NO_REPEAT is reduced to a NULL recurrence */ - return NULL; - } - else - { - GcalRecurrence *new_recur; - - if (old_recur) - { - new_recur = gcal_recurrence_copy (old_recur); - } - else - { - new_recur = gcal_recurrence_new (); - } - - new_recur->frequency = frequency; - - return new_recur; - } -} - -static GcalRecurrence * -recur_change_limit_type (GcalRecurrence *old_recur, GcalRecurrenceLimitType limit_type, GDateTime *date_start) -{ - /* An old recurrence would already be present with something other than GCAL_RECURRENCE_NO_REPEAT */ - g_assert (old_recur != NULL); - g_assert (old_recur->frequency != GCAL_RECURRENCE_NO_REPEAT); - - GcalRecurrence *new_recur = gcal_recurrence_copy (old_recur); - - if (limit_type == old_recur->limit_type) - { - return new_recur; - } - else - { - new_recur->limit_type = limit_type; - - switch (limit_type) - { - case GCAL_RECURRENCE_FOREVER: - break; - - case GCAL_RECURRENCE_COUNT: - /* Other policies are possible. This is just "more than once". */ - new_recur->limit.count = 2; - break; - - case GCAL_RECURRENCE_UNTIL: - /* Again, other policies are possible. For now, leave the decision to the user. */ - g_assert (date_start != NULL); - new_recur->limit.until = g_date_time_ref (date_start); - break; - - default: - g_assert_not_reached (); - } - } - - return new_recur; -} - -static GcalEventSchedule * -gcal_event_schedule_set_recur_frequency (const GcalEventSchedule *values, - GcalRecurrenceFrequency frequency) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - GcalRecurrence *new_recur = recur_change_frequency (copy->curr.recur, frequency); - g_clear_pointer (©->curr.recur, gcal_recurrence_unref); - copy->curr.recur = new_recur; - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_recur_limit_type (const GcalEventSchedule *values, - GcalRecurrenceLimitType limit_type) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - GcalRecurrence *new_recur = recur_change_limit_type (copy->curr.recur, limit_type, values->curr.date_start); - g_clear_pointer (©->curr.recur, gcal_recurrence_unref); - copy->curr.recur = new_recur; - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_recurrence_count (const GcalEventSchedule *values, - guint count) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - /* An old recurrence would already be present */ - g_assert (copy->curr.recur != NULL); - g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); - g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_COUNT); - - copy->curr.recur->limit.count = count; - - return copy; -} - -static GcalEventSchedule * -gcal_event_schedule_set_recurrence_until (const GcalEventSchedule *values, - GDateTime *until) -{ - GcalEventSchedule *copy = gcal_event_schedule_copy (values); - - /* An old recurrence would already be present */ - g_assert (copy->curr.recur != NULL); - g_assert (copy->curr.recur->frequency != GCAL_RECURRENCE_NO_REPEAT); - g_assert (copy->curr.recur->limit_type == GCAL_RECURRENCE_UNTIL); - - gcal_set_date_time (©->curr.recur->limit.until, until); - - return copy; -} - static WidgetState * widget_state_from_values (const GcalEventSchedule *values) { @@ -1104,186 +756,6 @@ gcal_schedule_section_day_changed (GcalScheduleSection *self) return day_changed (self->values); } -/** - * Extracts the values from @event that are needed to populate #GcalScheduleSection. - * - * Returns: a #GcalEventSchedule ready for use. Free it with - * gcal_event_schedule_free(). - */ -GcalEventSchedule * -gcal_event_schedule_from_event (GcalEvent *event, - GcalTimeFormat time_format) -{ - GcalScheduleValues values; - memset (&values, 0, sizeof (values)); - - if (event) - { - GcalRecurrence *recur = gcal_event_get_recurrence (event); - - values.all_day = gcal_event_get_all_day (event); - values.date_start = g_date_time_ref (gcal_event_get_date_start (event)); - values.date_end = g_date_time_ref (gcal_event_get_date_end (event)); - - if (recur) - { - values.recur = gcal_recurrence_copy (recur); - } - else - { - values.recur = NULL; - } - } - - GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); - - *section_values = (GcalEventSchedule) { - .orig = gcal_schedule_values_copy (&values), - .curr = values, - .time_format = time_format, - }; - - return section_values; -} - -/* Builds a new GcalscheduleSectgionValues from two ISO 8601 date-times; be sure to - * include your timezone if you need it. This function is just to be used from tests. - **/ -static GcalScheduleValues -values_with_date_times (const char *start, const char *end, gboolean all_day) -{ - g_autoptr (GDateTime) start_date = g_date_time_new_from_iso8601 (start, NULL); - g_assert (start_date != NULL); - - g_autoptr (GDateTime) end_date = g_date_time_new_from_iso8601 (end, NULL); - g_assert (end_date != NULL); - - g_assert (g_date_time_compare (start_date, end_date) == -1); - - GcalScheduleValues values = { - .all_day = all_day, - .date_start = g_date_time_ref (start_date), - .date_end = g_date_time_ref (end_date), - .recur = NULL, - }; - - return values; -} - -static GcalEventSchedule * -event_schedule_with_date_times (const char *start, const char *end, gboolean all_day) -{ - GcalScheduleValues values = values_with_date_times (start, end, all_day); - - GcalEventSchedule *section_values = g_new0 (GcalEventSchedule, 1); - - *section_values = (GcalEventSchedule) { - .orig = gcal_schedule_values_copy (&values), - .curr = values, - .time_format = GCAL_TIME_FORMAT_24H, - }; - - return section_values; -} - -static void -test_setting_start_date_after_end_date_resets_end_date (void) -{ - /* We start with - * start = 10:00 - * end = 11:00 - * - * Then we set start to 12:00 - * - * We want to test that end becomes 12:00 as well. - */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00", - FALSE); - - g_autoptr (GDateTime) two_hours_later = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); - - g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date (values, two_hours_later); - g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_later)); - g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_later)); -} - -static void -test_setting_end_date_before_start_date_resets_start_date (void) -{ - /* We start with - * start = 10:00 - * end = 11:00 - * - * Then we set end to 09:00 - * - * We want to test that start becomes 09:00 as well. - */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", - "20250303T11:00:00-06:00", - FALSE); - - g_autoptr (GDateTime) two_hours_earlier = g_date_time_new_from_iso8601 ("20250303T09:00:00-06:00", NULL); - - g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date (values, two_hours_earlier); - g_assert (g_date_time_equal (new_values->curr.date_start, two_hours_earlier)); - g_assert (g_date_time_equal (new_values->curr.date_end, two_hours_earlier)); -} - -static void -test_setting_start_datetime_preserves_end_timezone (void) -{ - /* Start with - * start = 10:00, UTC-6 - * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) - * - * Set the start date to 11:30, UTC-6 - * - * End date should be 13:30, UTC-5 (i.e. end_date was set to the same as start_date, but in a different tz) - * - * (imagine driving for one hour while crossing timezones) - */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00", - FALSE); - g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); - - g_autoptr (GDateTime) new_start = g_date_time_new_from_iso8601 ("20250303T11:30:00-06:00", NULL); - g_autoptr (GDateTime) expected_end = g_date_time_new_from_iso8601 ("20250303T13:30:00-05:00", NULL); - - g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_start_date_time (values, new_start); - g_assert (g_date_time_equal (new_values->curr.date_start, new_start)); - - g_assert (g_date_time_equal (new_values->curr.date_end, expected_end)); -} - -static void -test_setting_end_datetime_preserves_start_timezone (void) -{ - /* Start with - * start = 10:00, UTC-6 - * end = 11:30, UTC-5 (note the different timezone; event is 30 minutes long) - * - * Set the end date to 09:30, UTC-5 - * - * Start date should be 07:30, UTC-6 (set to one hour earlier than the end time, with the original start's tz) - * - * (imagine driving for one hour while crossing timezones) - */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250303T10:00:00-06:00", - "20250303T11:30:00-05:00", - FALSE); - g_assert (g_date_time_compare (values->curr.date_start, values->curr.date_end) == -1); - - g_autoptr (GDateTime) new_end = g_date_time_new_from_iso8601 ("20250303T09:30:00-05:00", NULL); - g_autoptr (GDateTime) expected_start = g_date_time_new_from_iso8601 ("20250303T07:30:00-06:00", NULL); - - g_autoptr (GcalEventSchedule) new_values = gcal_event_schedule_set_end_date_time (values, new_end); - g_assert (g_date_time_equal (new_values->curr.date_end, new_end)); - - g_assert (g_date_time_equal (new_values->curr.date_start, expected_start)); -} - static void test_all_day_displays_sensible_dates_and_roundtrips (void) { @@ -1301,9 +773,9 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) * Check that the values gets back end = (start + 2 days) */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times ("20250303T00:00:00-06:00", - "20250304T00:00:00-06:00", - TRUE); + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times ("20250303T00:00:00-06:00", + "20250304T00:00:00-06:00", + TRUE); /* Compute the widget state from the values; check the displayed dates (should be the same) */ @@ -1337,88 +809,13 @@ test_all_day_displays_sensible_dates_and_roundtrips (void) g_assert_cmpint (g_date_time_get_day_of_month (new_state->date_time_end), ==, 4); } -static void -test_43_switching_to_all_day_preserves_timezones (void) -{ - g_test_bug ("43"); - /* https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/43 - * - * Given an event that is not all-day, e.g. from $day1-12:00 to $day2-13:00, if one turns it to an - * all-day event, we want to switch the dates like this: - * - * start: $day1's 00:00 - * end: $day2's 24:00 (e.g. ($day2 + 1)'s 00:00) - * - * Also test that after that, changing the dates and turning off all-day restores the - * original times. - */ - - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times ("20250303T12:00:00-06:00", - "20250304T13:00:00-06:00", - FALSE); - - /* set all-day and check that this modified the times to midnight */ - - g_autoptr (GcalEventSchedule) all_day_values = gcal_event_schedule_set_all_day (values, TRUE); - - g_autoptr (GDateTime) expected_start_date = g_date_time_new_from_iso8601 ("20250303T00:00:00-06:00", NULL); - g_autoptr (GDateTime) expected_end_date = g_date_time_new_from_iso8601 ("20250305T00:00:00-06:00", NULL); - - g_assert (g_date_time_equal (all_day_values->curr.date_start, expected_start_date)); - g_assert (g_date_time_equal (all_day_values->curr.date_end, expected_end_date)); - - /* turn off all-day; check that times were restored */ - - g_autoptr (GcalEventSchedule) not_all_day_values = gcal_event_schedule_set_all_day (all_day_values, FALSE); - - g_autoptr (GDateTime) expected_start_date2 = g_date_time_new_from_iso8601 ("20250303T12:00:00-06:00", NULL); - g_autoptr (GDateTime) expected_end_date2 = g_date_time_new_from_iso8601 ("20250304T13:00:00-06:00", NULL); - - g_assert (g_date_time_equal (not_all_day_values->curr.date_start, expected_start_date2)); - g_assert (g_date_time_equal (not_all_day_values->curr.date_end, expected_end_date2)); -} - -static void -test_recur_changes_to_no_repeat (void) -{ - g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); - old_recur->frequency = GCAL_RECURRENCE_WEEKLY; - - g_autoptr (GcalRecurrence) new_recur = recur_change_frequency (old_recur, GCAL_RECURRENCE_NO_REPEAT); - g_assert_null (new_recur); -} - -static void -test_recur_changes_limit_type_to_count (void) -{ - g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); - old_recur->frequency = GCAL_RECURRENCE_WEEKLY; - - g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_COUNT, NULL); - g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_COUNT); - g_assert_cmpint (new_recur->limit.count, ==, 2); -} - -static void -test_recur_changes_limit_type_to_until (void) -{ - g_autoptr (GcalRecurrence) old_recur = gcal_recurrence_new (); - old_recur->frequency = GCAL_RECURRENCE_WEEKLY; - - g_autoptr (GDateTime) date = g_date_time_new_from_iso8601 ("20250411T00:00:00-06:00", NULL); - - g_autoptr (GcalRecurrence) new_recur = recur_change_limit_type (old_recur, GCAL_RECURRENCE_UNTIL, date); - g_assert_cmpint (new_recur->limit_type, ==, GCAL_RECURRENCE_UNTIL); - g_assert (g_date_time_equal (new_recur->limit.until, date)); -} - static void test_turning_on_recurrence_count_turns_on_its_widget (void) { /* Start with some configuration */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250411T10:00:00-06:00", - "20250411T11:30:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); /* Turn on recurrence */ g_autoptr (GcalEventSchedule) with_recur = @@ -1438,9 +835,9 @@ static void test_turning_on_recurrence_until_turns_on_its_widget (void) { /* Start with some configuration */ - g_autoptr (GcalEventSchedule) values = event_schedule_with_date_times("20250411T10:00:00-06:00", - "20250411T11:30:00-06:00", - FALSE); + g_autoptr (GcalEventSchedule) values = gcal_event_schedule_with_date_times("20250411T10:00:00-06:00", + "20250411T11:30:00-06:00", + FALSE); /* Turn on recurrence */ g_autoptr (GcalEventSchedule) with_recur = @@ -1459,24 +856,8 @@ test_turning_on_recurrence_until_turns_on_its_widget (void) void gcal_schedule_section_add_tests (void) { - g_test_add_func ("/event_editor/schedule_section/setting_start_date_after_end_date_resets_end_date", - test_setting_start_date_after_end_date_resets_end_date); - g_test_add_func ("/event_editor/schedule_section/setting_end_date_before_start_date_resets_start_date", - test_setting_end_date_before_start_date_resets_start_date); - g_test_add_func ("/event_editor/schedule_section/setting_start_datetime_preserves_end_timezone", - test_setting_start_datetime_preserves_end_timezone); - g_test_add_func ("/event_editor/schedule_section/setting_end_datetime_preserves_start_timezone", - test_setting_end_datetime_preserves_start_timezone); g_test_add_func ("/event_editor/schedule_section/all_day_displays_sensible_dates_and_roundtrips", test_all_day_displays_sensible_dates_and_roundtrips); - g_test_add_func ("/event_editor/schedule_section/43_switching_to_all_day_preserves_timezones", - test_43_switching_to_all_day_preserves_timezones); - g_test_add_func ("/event_editor/schedule_section/recur_changes_to_no_repeat", - test_recur_changes_to_no_repeat); - g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_count", - test_recur_changes_limit_type_to_count); - g_test_add_func ("/event_editor/schedule_section/recur_changes_limit_type_to_until", - test_recur_changes_limit_type_to_until); g_test_add_func ("/event_editor/schedule_section/turning_on_recurrence_count_turns_on_its_widget", test_turning_on_recurrence_count_turns_on_its_widget); g_test_add_func ("/event_editor/schedule_section/turning_on_recurrence_until_turns_on_its_widget", diff --git a/src/gui/event-editor/gcal-schedule-section.h b/src/gui/event-editor/gcal-schedule-section.h index eebea5920..e17f0be55 100644 --- a/src/gui/event-editor/gcal-schedule-section.h +++ b/src/gui/event-editor/gcal-schedule-section.h @@ -32,42 +32,10 @@ G_BEGIN_DECLS #define GCAL_TYPE_SCHEDULE_SECTION (gcal_schedule_section_get_type()) G_DECLARE_FINAL_TYPE (GcalScheduleSection, gcal_schedule_section, GCAL, SCHEDULE_SECTION, GtkBox) -/* Values from a GcalEvent that this widget can manipulate. - * - * We keep an immutable copy of the original event's values, and later generate - * a new GcalScheduleValues with the updated data from the widgetry. - */ -typedef struct -{ - gboolean all_day; - - GDateTime *date_start; - GDateTime *date_end; - - GcalRecurrence *recur; -} GcalScheduleValues; - -typedef struct -{ - /* original values from event */ - GcalScheduleValues orig; - - /* current values, as modified by actions from widgets */ - GcalScheduleValues curr; - - /* copied from GcalContext to avoid a dependency on it */ - GcalTimeFormat time_format; -} GcalEventSchedule; - gboolean gcal_schedule_section_recurrence_changed (GcalScheduleSection *self); gboolean gcal_schedule_section_day_changed (GcalScheduleSection *self); -GcalEventSchedule *gcal_event_schedule_from_event (GcalEvent *event, - GcalTimeFormat time_format); -void gcal_event_schedule_free (GcalEventSchedule *values); - -G_DEFINE_AUTOPTR_CLEANUP_FUNC (GcalEventSchedule, gcal_event_schedule_free); /* Tests */ diff --git a/src/gui/event-editor/meson.build b/src/gui/event-editor/meson.build index 01ab83643..52d2c3d11 100644 --- a/src/gui/event-editor/meson.build +++ b/src/gui/event-editor/meson.build @@ -39,6 +39,7 @@ sources += files( 'gcal-date-chooser-row.c', 'gcal-date-time-chooser.c', 'gcal-date-selector.c', + 'gcal-event-schedule.c', 'gcal-event-editor-dialog.c', 'gcal-event-editor-section.c', 'gcal-multi-choice.c', diff --git a/src/gui/gcal-tests.c b/src/gui/gcal-tests.c index 979ba9fcb..acadea7f0 100644 --- a/src/gui/gcal-tests.c +++ b/src/gui/gcal-tests.c @@ -1,6 +1,7 @@ #include #include "gcal-tests.h" +#include "event-editor/gcal-event-schedule.h" #include "event-editor/gcal-schedule-section.h" /* Adds all the tests for the internals of the GUI. @@ -11,5 +12,6 @@ void gcal_tests_add_internals (void) { + gcal_event_schedule_add_tests (); gcal_schedule_section_add_tests (); } -- GitLab