From 2d9afa938f752549381d010c4d36d218ad31a9b8 Mon Sep 17 00:00:00 2001 From: FineFindus Date: Sat, 12 Jul 2025 15:32:55 +0200 Subject: [PATCH] gui/edit-calendar-page: allow exporting calendars Adds an export option to the edit page, allowing users to export an entire calendar at once. Closes: https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/31 Co-authored-by: Krifa75 Part-of: --- .../gcal-edit-calendar-page.blp | 9 +- .../gcal-edit-calendar-page.c | 137 +++++++++++++++++- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/src/gui/calendar-management/gcal-edit-calendar-page.blp b/src/gui/calendar-management/gcal-edit-calendar-page.blp index 1e236191..d5643da2 100644 --- a/src/gui/calendar-management/gcal-edit-calendar-page.blp +++ b/src/gui/calendar-management/gcal-edit-calendar-page.blp @@ -77,7 +77,14 @@ template $GcalEditCalendarPage: Adw.NavigationPage { } } - Adw.PreferencesGroup remove_group { + Adw.PreferencesGroup { + Adw.ButtonRow { + title: _("_Export Calendar"); + end-icon-name: "go-next-symbolic"; + use-underline: true; + activated => $on_export_button_row_activated_cb(template) not-swapped; + } + Adw.ButtonRow remove_button_row { title: _("_Remove Calendar"); use-underline: true; diff --git a/src/gui/calendar-management/gcal-edit-calendar-page.c b/src/gui/calendar-management/gcal-edit-calendar-page.c index 89878ab2..bc1a3034 100644 --- a/src/gui/calendar-management/gcal-edit-calendar-page.c +++ b/src/gui/calendar-management/gcal-edit-calendar-page.c @@ -37,7 +37,7 @@ struct _GcalEditCalendarPage AdwSwitchRow *calendar_visible_row; AdwActionRow *calendar_url_row; AdwEntryRow *name_entry; - GtkWidget *remove_group; + GtkWidget *remove_button_row; GcalCalendar *calendar; @@ -202,7 +202,7 @@ setup_calendar (GcalEditCalendarPage *self, gtk_editable_set_text (GTK_EDITABLE (self->name_entry), gcal_calendar_get_name (calendar)); adw_switch_row_set_active (self->calendar_visible_row, gcal_calendar_get_visible (calendar)); - gtk_widget_set_visible (self->remove_group, e_source_get_removable (source)); + gtk_widget_set_visible (self->remove_button_row, e_source_get_removable (source)); } /* @@ -225,6 +225,136 @@ on_calendar_visibility_changed_cb (AdwSwitchRow *row, gcal_calendar_set_visible (self->calendar, adw_switch_row_get_active (row)); } +typedef struct +{ + GHashTable *zones; + ECalClient *client; +} TimeZoneData; + +static void +insert_tz_comps (ICalParameter *param, + gpointer data) +{ + g_autoptr (GError) error = NULL; + TimeZoneData *tdata = data; + ICalTimezone *zone = NULL; + ICalComponent *tzcomp; + const gchar *tzid; + + tzid = i_cal_parameter_get_tzid (param); + + if (g_hash_table_lookup (tdata->zones, tzid)) + return; + + if (!e_cal_client_get_timezone_sync (tdata->client, tzid, &zone, NULL, &error)) + zone = NULL; + + if (error != NULL) + { + g_warning ("Could not get the timezone information for %s: %s", tzid, error->message); + return; + } + + tzcomp = i_cal_component_clone (i_cal_timezone_get_component (zone)); + g_hash_table_insert (tdata->zones, (gpointer) tzid, (gpointer) tzcomp); +} + +static void +append_tz_to_comp (gpointer key, + gpointer value, + ICalComponent *toplevel) +{ + i_cal_component_add_component (toplevel, (ICalComponent *) value); +} + +static void +on_file_dialog_save_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GcalEditCalendarPage *self = user_data; + g_autofree gchar *ics_str = NULL; + g_autoptr (GError) error = NULL; + g_autoptr (GSList) events = NULL; + g_autoptr (GFile) file = NULL; + ICalComponent *toplevel_component = NULL; + ECalClient *client = NULL; + TimeZoneData tz_data; + + file = gtk_file_dialog_save_finish (GTK_FILE_DIALOG (object), res, &error); + if (error) + { + if (!g_error_matches (error, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_DISMISSED)) + g_warning ("Error saving file: %s", error->message); + return; + } + + client = gcal_calendar_get_client (self->calendar); + e_cal_client_get_object_list_sync (client, "#t", &events, NULL, NULL); + + /* Avoid exporting empty calendars */ + if (!events) + return; + + toplevel_component = e_cal_util_new_top_level (); + tz_data.zones = g_hash_table_new (g_str_hash, g_str_equal); + tz_data.client = client; + + for (GSList *l = events; l != NULL; l = l->next) + { + ICalComponent *component = i_cal_component_clone (l->data); + + i_cal_component_foreach_tzid (component, insert_tz_comps, &tz_data); + i_cal_component_take_component (toplevel_component, g_steal_pointer (&component)); + } + + g_hash_table_foreach (tz_data.zones, (GHFunc) append_tz_to_comp, toplevel_component); + g_clear_pointer (&tz_data.zones, g_hash_table_destroy); + tz_data.zones = NULL; + + i_cal_component_strip_errors (toplevel_component); + ics_str = i_cal_component_as_ical_string (toplevel_component); + + g_clear_pointer (&toplevel_component, i_cal_component_free); + + g_file_replace_contents (file, + ics_str, + strlen (ics_str), + NULL, + FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, + NULL, + NULL, + NULL); +} + +static void +on_export_button_row_activated_cb (GtkButton *button, + GcalEditCalendarPage *self) +{ + g_autofree gchar *filename = NULL; + GtkFileDialog *file_dialog = NULL; + GtkWindow *window = NULL; + + GCAL_ENTRY; + + window = GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))); + file_dialog = gtk_file_dialog_new (); + filename = g_strconcat (gcal_calendar_get_name (self->calendar), ".ics", NULL); + + gtk_file_dialog_set_title (file_dialog, _("Export Calendar")); + gtk_file_dialog_set_initial_name (file_dialog, filename); + + gtk_file_dialog_save (file_dialog, + window, + NULL, + on_file_dialog_save_cb, + self); + + + GCAL_EXIT; +} + static void on_remove_button_row_activated_cb (GtkButton *button, GcalEditCalendarPage *self) @@ -395,11 +525,12 @@ gcal_edit_calendar_page_class_init (GcalEditCalendarPageClass *klass) gtk_widget_class_bind_template_child (widget_class, GcalEditCalendarPage, calendar_url_row); gtk_widget_class_bind_template_child (widget_class, GcalEditCalendarPage, calendar_visible_row); gtk_widget_class_bind_template_child (widget_class, GcalEditCalendarPage, name_entry); - gtk_widget_class_bind_template_child (widget_class, GcalEditCalendarPage, remove_group); + gtk_widget_class_bind_template_child (widget_class, GcalEditCalendarPage, remove_button_row); gtk_widget_class_bind_template_callback (widget_class, on_calendar_color_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_calendar_visibility_changed_cb); gtk_widget_class_bind_template_callback (widget_class, on_remove_button_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_export_button_row_activated_cb); gtk_widget_class_bind_template_callback (widget_class, on_settings_button_clicked_cb); }