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 00000000..ed4694dc --- /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 00000000..a7a83c52 --- /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 6dc7ba63..b9bea1d1 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 eebea592..e17f0be5 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 01ab8364..52d2c3d1 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 979ba9fc..acadea7f 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 (); }