diff options
author | Georges Basile Stavracas Neto <georges.stavracas@gmail.com> | 2021-08-16 15:27:46 -0300 |
---|---|---|
committer | Georges Basile Stavracas Neto <georges.stavracas@gmail.com> | 2021-08-16 18:26:21 -0300 |
commit | e689f18b1e5847a74772d5243721f035386e3565 (patch) | |
tree | cdd136efeaca8a6f4200328d60e2e44a0618c37e | |
parent | a080d8ec0f5b782d26fb849909311454d04a8ab8 (diff) | |
download | gnome-calendar-e689f18b1e5847a74772d5243721f035386e3565.tar.gz |
Add file importing dialog
Add a file importing dialog, and pass through files received from command
line.
Fixes https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/5
-rw-r--r-- | po/POTFILES.in | 4 | ||||
-rw-r--r-- | src/gui/gcal-application.c | 54 | ||||
-rw-r--r-- | src/gui/gcal-window.c | 17 | ||||
-rw-r--r-- | src/gui/gcal-window.h | 4 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-dialog.c | 569 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-dialog.h | 36 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-dialog.ui | 197 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-file-row.c | 388 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-file-row.h | 37 | ||||
-rw-r--r-- | src/gui/importer/gcal-import-file-row.ui | 43 | ||||
-rw-r--r-- | src/gui/importer/gcal-importer.c | 173 | ||||
-rw-r--r-- | src/gui/importer/gcal-importer.h | 40 | ||||
-rw-r--r-- | src/gui/importer/importer.gresource.xml | 7 | ||||
-rw-r--r-- | src/gui/importer/meson.build | 13 | ||||
-rw-r--r-- | src/gui/meson.build | 1 |
15 files changed, 1582 insertions, 1 deletions
diff --git a/po/POTFILES.in b/po/POTFILES.in index f242f27a..4fba264b 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -34,6 +34,10 @@ src/gui/gcal-weather-settings.ui src/gui/gcal-window.c src/gui/gcal-window.ui src/gui/gtk/help-overlay.ui +src/gui/importer/gcal-import-dialog.c +src/gui/importer/gcal-import-dialog.ui +src/gui/importer/gcal-importer.c +src/gui/importer/gcal-import-file-row.c src/gui/views/gcal-month-popover.ui src/gui/views/gcal-week-grid.c src/gui/views/gcal-week-header.c diff --git a/src/gui/gcal-application.c b/src/gui/gcal-application.c index 99356732..84e38b6d 100644 --- a/src/gui/gcal-application.c +++ b/src/gui/gcal-application.c @@ -457,11 +457,14 @@ gcal_application_command_line (GApplication *app, GApplicationCommandLine *command_line) { g_autoptr (GVariant) option = NULL; + g_auto (GStrv) arguments = NULL; GcalApplication *self; GVariantDict *options; const gchar* date = NULL; const gchar* uuid = NULL; gsize length; + gint n_arguments; + gint i; GCAL_ENTRY; @@ -510,6 +513,37 @@ gcal_application_command_line (GApplication *app, g_application_activate (app); + arguments = g_application_command_line_get_arguments (command_line, &n_arguments); + if (n_arguments > 1) + { + g_autoptr (GHashTable) unique_files = NULL; + g_autoptr (GPtrArray) files = NULL; + gint n_files = 0; + + files = g_ptr_array_new_full (n_arguments - 1, g_object_unref); + unique_files = g_hash_table_new (g_file_hash, (GEqualFunc) g_file_equal); + + for (i = 1; i < n_arguments; i++) + { + g_autoptr (GFile) file = NULL; + const gchar *arg; + + arg = arguments[i]; + file = g_application_command_line_create_file_for_arg (command_line, arg); + + if (g_str_has_prefix (arg, "-") || g_hash_table_contains (unique_files, file)) + continue; + + g_hash_table_add (unique_files, file); + g_ptr_array_add (files, g_steal_pointer (&file)); + + n_files++; + } + + if (n_files > 0) + g_application_open (app, (GFile **) files->pdata, n_files, ""); + } + GCAL_RETURN (0); } @@ -575,6 +609,23 @@ gcal_application_dbus_unregister (GApplication *application, } static void +gcal_application_open (GApplication *application, + GFile **files, + gint n_files, + const gchar *hint) +{ + GcalApplication *self; + + GCAL_ENTRY; + + self = GCAL_APPLICATION (application); + + gcal_window_import_files (GCAL_WINDOW (self->window), files, n_files); + + GCAL_EXIT; +} + +static void gcal_application_class_init (GcalApplicationClass *klass) { GObjectClass *object_class; @@ -588,6 +639,7 @@ gcal_application_class_init (GcalApplicationClass *klass) application_class->activate = gcal_application_activate; application_class->startup = gcal_application_startup; application_class->command_line = gcal_application_command_line; + application_class->open = gcal_application_open; application_class->handle_local_options = gcal_application_handle_local_options; application_class->dbus_register = gcal_application_dbus_register; application_class->dbus_unregister = gcal_application_dbus_unregister; @@ -631,7 +683,7 @@ gcal_application_new (void) return g_object_new (gcal_application_get_type (), "resource-base-path", "/org/gnome/calendar", "application-id", APPLICATION_ID, - "flags", G_APPLICATION_HANDLES_COMMAND_LINE, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, NULL); } diff --git a/src/gui/gcal-window.c b/src/gui/gcal-window.c index 65183985..0767db65 100644 --- a/src/gui/gcal-window.c +++ b/src/gui/gcal-window.c @@ -38,6 +38,8 @@ #include "gcal-window.h" #include "gcal-year-view.h" +#include "importer/gcal-import-dialog.h" + #include <glib/gi18n.h> #include <libecal/libecal.h> @@ -116,6 +118,7 @@ struct _GcalWindow GtkWidget *views_switcher; GcalEventEditorDialog *event_editor; + GtkWidget *import_dialog; DzlSuggestionButton *search_button; @@ -1205,3 +1208,17 @@ gcal_window_open_event_by_uuid (GcalWindow *self, edit_dialog_data); } } + +void +gcal_window_import_files (GcalWindow *self, + GFile **files, + gint n_files) +{ + g_return_if_fail (GCAL_IS_WINDOW (self)); + + g_clear_pointer (&self->import_dialog, gtk_widget_destroy); + + self->import_dialog = gcal_import_dialog_new_for_files (self->context, files, n_files); + gtk_window_set_transient_for (GTK_WINDOW (self->import_dialog), GTK_WINDOW (self)); + gtk_window_present (GTK_WINDOW (self->import_dialog)); +} diff --git a/src/gui/gcal-window.h b/src/gui/gcal-window.h index 4264e4b2..efde2434 100644 --- a/src/gui/gcal-window.h +++ b/src/gui/gcal-window.h @@ -41,6 +41,10 @@ void gcal_window_set_search_query (GcalWindow void gcal_window_open_event_by_uuid (GcalWindow *self, const gchar *uuid); +void gcal_window_import_files (GcalWindow *self, + GFile **files, + gint n_files); + G_END_DECLS #endif /* __GCAL_WINDOW_H__ */ diff --git a/src/gui/importer/gcal-import-dialog.c b/src/gui/importer/gcal-import-dialog.c new file mode 100644 index 00000000..ef37e6bf --- /dev/null +++ b/src/gui/importer/gcal-import-dialog.c @@ -0,0 +1,569 @@ +/* gcal-import-dialog.c + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalImportDialog" + +#include "gcal-import-dialog.h" + +#include "config.h" +#include "gcal-debug.h" +#include "gcal-import-file-row.h" +#include "gcal-utils.h" + +#include <glib/gi18n.h> + +struct _GcalImportDialog +{ + HdyWindow parent; + + GtkImage *calendar_color_image; + GtkLabel *calendar_name_label; + GtkListBox *calendars_listbox; + GtkPopover *calendars_popover; + GtkWidget *cancel_button; + GtkListBox *files_listbox; + HdyHeaderBar *headerbar; + GtkWidget *import_button; + GtkSizeGroup *title_sizegroup; + + GtkWidget *selected_row; + + GCancellable *cancellable; + GcalContext *context; + gint n_events; + gint n_files; +}; + + +static void on_import_row_file_loaded_cb (GcalImportFileRow *row, + GPtrArray *events, + GcalImportDialog *self); + +static void on_manager_calendar_added_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self); + +static void on_manager_calendar_changed_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self); + +static void on_manager_calendar_removed_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self); + +G_DEFINE_TYPE (GcalImportDialog, gcal_import_dialog, HDY_TYPE_WINDOW) + +enum +{ + PROP_0, + PROP_CONTEXT, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + + +/* + * Auxiliary methods + */ + +static GtkWidget* +create_calendar_row (GcalManager *manager, + GcalCalendar *calendar) +{ + g_autofree gchar *parent_name = NULL; + cairo_surface_t *surface; + const GdkRGBA *color; + GtkWidget *icon; + GtkWidget *row; + + color = gcal_calendar_get_color (calendar); + surface = get_circle_surface_from_color (color, 16); + get_source_parent_name_color (manager, + gcal_calendar_get_source (calendar), + &parent_name, + NULL); + + /* The icon with the source color */ + icon = gtk_image_new_from_surface (surface); + gtk_style_context_add_class (gtk_widget_get_style_context (icon), "calendar-color-image"); + gtk_widget_show (icon); + + /* The row itself */ + row = g_object_new (HDY_TYPE_ACTION_ROW, + "title", gcal_calendar_get_name (calendar), + "subtitle", parent_name, + "sensitive", !gcal_calendar_is_read_only (calendar), + "activatable", TRUE, + "width-request", 300, + NULL); + hdy_action_row_add_prefix (HDY_ACTION_ROW (row), icon); + gtk_widget_show (row); + + g_object_set_data_full (G_OBJECT (row), "calendar", g_object_ref (calendar), g_object_unref); + g_object_set_data (G_OBJECT (row), "color-icon", icon); + + g_clear_pointer (&surface, cairo_surface_destroy); + + return row; +} + +static GtkWidget* +get_row_for_calendar (GcalImportDialog *self, + GcalCalendar *calendar) +{ + g_autoptr (GList) children = NULL; + GtkWidget *row; + GList *l; + + row = NULL; + children = gtk_container_get_children (GTK_CONTAINER (self->calendars_listbox)); + + for (l = children; l != NULL; l = g_list_next (l)) + { + GcalCalendar *row_calendar = g_object_get_data (l->data, "calendar"); + + if (row_calendar == calendar) + { + row = l->data; + break; + } + } + + return row; +} + +static void +select_row (GcalImportDialog *self, + GtkListBoxRow *row) +{ + cairo_surface_t *surface; + const GdkRGBA *color; + GcalCalendar *calendar; + + self->selected_row = GTK_WIDGET (row); + + /* Setup the event page's source name and color */ + calendar = g_object_get_data (G_OBJECT (row), "calendar"); + + gtk_label_set_label (self->calendar_name_label, gcal_calendar_get_name (calendar)); + + color = gcal_calendar_get_color (calendar); + surface = get_circle_surface_from_color (color, 16); + gtk_image_set_from_surface (self->calendar_color_image, surface); + + g_clear_pointer (&surface, cairo_surface_destroy); +} + +static void +update_default_calendar_row (GcalImportDialog *self) +{ + GcalCalendar *default_calendar; + GcalManager *manager; + GtkWidget *row; + + manager = gcal_context_get_manager (self->context); + default_calendar = gcal_manager_get_default_calendar (manager); + + row = get_row_for_calendar (self, default_calendar); + if (row != NULL) + select_row (self, GTK_LIST_BOX_ROW (row)); +} + +static void +setup_calendars (GcalImportDialog *self) +{ + g_autoptr (GList) calendars = NULL; + GcalManager *manager; + GList *l; + + manager = gcal_context_get_manager (self->context); + calendars = gcal_manager_get_calendars (manager); + + for (l = calendars; l; l = l->next) + on_manager_calendar_added_cb (manager, l->data, self); + + update_default_calendar_row (self); + + g_signal_connect_object (manager, "calendar-added", G_CALLBACK (on_manager_calendar_added_cb), self, 0); + g_signal_connect_object (manager, "calendar-changed", G_CALLBACK (on_manager_calendar_changed_cb), self, 0); + g_signal_connect_object (manager, "calendar-removed", G_CALLBACK (on_manager_calendar_removed_cb), self, 0); + g_signal_connect_object (manager, "notify::default-calendar", G_CALLBACK (update_default_calendar_row), self, G_CONNECT_SWAPPED); +} + +static void +setup_files (GcalImportDialog *self, + GFile **files, + gint n_files) +{ + gint i; + + GCAL_ENTRY; + + self->n_files = n_files; + + for (i = 0; i < n_files; i++) + { + GtkWidget *row; + + row = gcal_import_file_row_new (files[i], self->title_sizegroup); + g_signal_connect (row, "file-loaded", G_CALLBACK (on_import_row_file_loaded_cb), self); + + if (n_files > 1) + gcal_import_file_row_show_filename (GCAL_IMPORT_FILE_ROW (row)); + + gtk_list_box_insert (self->files_listbox, row, -1); + } + + GCAL_EXIT; +} + + +/* + * Callbacks + */ + +static void +on_events_created_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr (GError) error = NULL; + GcalImportDialog *self; + + GCAL_ENTRY; + + self = GCAL_IMPORT_DIALOG (user_data); + + e_cal_client_create_objects_finish (E_CAL_CLIENT (source_object), result, NULL, &error); + if (error) + g_warning ("Error creating events: %s", error->message); + + gtk_widget_destroy (GTK_WIDGET (self)); + + GCAL_EXIT; +} + +static void +on_calendars_listbox_row_activated_cb (GtkListBox *listbox, + GtkListBoxRow *row, + GcalImportDialog *self) +{ + GCAL_ENTRY; + + select_row (self, row); + gtk_popover_popdown (self->calendars_popover); + + GCAL_EXIT; +} + +static void +on_cancel_button_clicked_cb (GtkButton *button, + GcalImportDialog *self) +{ + gtk_widget_destroy (GTK_WIDGET (self)); +} + +static void +on_import_button_clicked_cb (GtkButton *button, + GcalImportDialog *self) +{ + g_autoptr (GList) children = NULL; + GcalCalendar *calendar; + ECalClient *client; + GSList *slist; + GList *l; + + GCAL_ENTRY; + + calendar = g_object_get_data (G_OBJECT (self->selected_row), "calendar"); + g_assert (self->selected_row != NULL); + + slist = NULL; + children = gtk_container_get_children (GTK_CONTAINER (self->files_listbox)); + for (l = children; l; l = l->next) + { + GcalImportFileRow *row = l->data; + GPtrArray *ical_components; + guint i; + + ical_components = gcal_import_file_row_get_ical_components (row); + if (!ical_components) + continue; + + for (i = 0; i < ical_components->len; i++) + slist = g_slist_prepend (slist, g_ptr_array_index (ical_components, i)); + } + + if (!slist) + GCAL_RETURN (); + + self->cancellable = g_cancellable_new (); + + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + + client = gcal_calendar_get_client (calendar); + e_cal_client_create_objects (client, + slist, + E_CAL_OPERATION_FLAG_NONE, + self->cancellable, + on_events_created_cb, + self); + + GCAL_EXIT; +} + +static void +on_import_row_file_loaded_cb (GcalImportFileRow *row, + GPtrArray *events, + GcalImportDialog *self) +{ + g_autofree gchar *title = NULL; + + GCAL_ENTRY; + + self->n_events += events ? events->len : 0; + + title = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, + "Import %d event", + "Import %d events", + self->n_events), + self->n_events); + hdy_header_bar_set_title (self->headerbar, title); + + gtk_widget_show (GTK_WIDGET (row)); + + GCAL_EXIT; +} + +static void +on_manager_calendar_added_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self) +{ + if (gcal_calendar_is_read_only (calendar)) + return; + + gtk_container_add (GTK_CONTAINER (self->calendars_listbox), + create_calendar_row (manager, calendar)); +} + +static void +on_manager_calendar_changed_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self) +{ + cairo_surface_t *surface; + const GdkRGBA *color; + GtkWidget *row, *color_icon; + gboolean read_only; + + read_only = gcal_calendar_is_read_only (calendar); + row = get_row_for_calendar (self, calendar); + + /* If the calendar changed from/to read-only, we add or remove it here */ + if (read_only) + { + if (row) + gtk_container_remove (GTK_CONTAINER (self->calendars_listbox), row); + return; + } + else if (!row) + { + on_manager_calendar_added_cb (manager, calendar, self); + row = get_row_for_calendar (self, calendar); + } + + hdy_preferences_row_set_title (HDY_PREFERENCES_ROW (row), gcal_calendar_get_name (calendar)); + gtk_widget_set_sensitive (row, !read_only); + + /* Setup the source color, in case it changed */ + color = gcal_calendar_get_color (calendar); + surface = get_circle_surface_from_color (color, 16); + color_icon = g_object_get_data (G_OBJECT (row), "color-icon"); + gtk_image_set_from_surface (GTK_IMAGE (color_icon), surface); + + gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->calendars_listbox)); + + g_clear_pointer (&surface, cairo_surface_destroy); +} + +static void +on_manager_calendar_removed_cb (GcalManager *manager, + GcalCalendar *calendar, + GcalImportDialog *self) +{ + GtkWidget *row; + + row = get_row_for_calendar (self, calendar); + + if (!row) + return; + + gtk_container_remove (GTK_CONTAINER (self->calendars_listbox), row); +} + +static void +on_select_calendar_row_activated_cb (GtkListBox *listbox, + GtkListBoxRow *row, + GcalImportDialog *self) +{ + gtk_popover_popup (self->calendars_popover); +} + +static gint +sort_func (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer user_data) +{ + GcalCalendar *calendar1, *calendar2; + g_autofree gchar *name1 = NULL; + g_autofree gchar *name2 = NULL; + + calendar1 = g_object_get_data (G_OBJECT (row1), "calendar"); + calendar2 = g_object_get_data (G_OBJECT (row2), "calendar"); + + name1 = g_utf8_casefold (gcal_calendar_get_name (calendar1), -1); + name2 = g_utf8_casefold (gcal_calendar_get_name (calendar2), -1); + + return g_strcmp0 (name1, name2); +} + + +/* + * GObject overrides + */ + +static void +gcal_import_dialog_finalize (GObject *object) +{ + GcalImportDialog *self = (GcalImportDialog *)object; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->context); + + G_OBJECT_CLASS (gcal_import_dialog_parent_class)->finalize (object); +} + +static void +gcal_import_dialog_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalImportDialog *self = GCAL_IMPORT_DIALOG (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_object (value, self->context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_import_dialog_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalImportDialog *self = GCAL_IMPORT_DIALOG (object); + + switch (prop_id) + { + case PROP_CONTEXT: + g_assert (self->context == NULL); + self->context = g_value_dup_object (value); + setup_calendars (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_import_dialog_class_init (GcalImportDialogClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_import_dialog_finalize; + object_class->get_property = gcal_import_dialog_get_property; + object_class->set_property = gcal_import_dialog_set_property; + + /** + * GcalEventPopover::context: + * + * The context of the import dialog. + */ + properties[PROP_CONTEXT] = g_param_spec_object ("context", + "Context", + "Context", + GCAL_TYPE_CONTEXT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/importer/gcal-import-dialog.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, calendar_color_image); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, calendar_name_label); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, calendars_listbox); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, calendars_popover); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, cancel_button); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, files_listbox); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, headerbar); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, import_button); + gtk_widget_class_bind_template_child (widget_class, GcalImportDialog, title_sizegroup); + + gtk_widget_class_bind_template_callback (widget_class, on_calendars_listbox_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_cancel_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_import_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, on_select_calendar_row_activated_cb); +} + +static void +gcal_import_dialog_init (GcalImportDialog *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->calendars_listbox), sort_func, NULL, NULL); +} + +GtkWidget* +gcal_import_dialog_new_for_files (GcalContext *context, + GFile **files, + gint n_files) +{ + GcalImportDialog *self; + + self = g_object_new (GCAL_TYPE_IMPORT_DIALOG, + "context", context, + NULL); + + setup_files (self, files, n_files); + + return GTK_WIDGET (self); +} diff --git a/src/gui/importer/gcal-import-dialog.h b/src/gui/importer/gcal-import-dialog.h new file mode 100644 index 00000000..89d70c5a --- /dev/null +++ b/src/gui/importer/gcal-import-dialog.h @@ -0,0 +1,36 @@ +/* gcal-import-dialog.h + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "gcal-context.h" + +#include <handy.h> + +G_BEGIN_DECLS + +#define GCAL_TYPE_IMPORT_DIALOG (gcal_import_dialog_get_type()) +G_DECLARE_FINAL_TYPE (GcalImportDialog, gcal_import_dialog, GCAL, IMPORT_DIALOG, HdyWindow) + +GtkWidget* gcal_import_dialog_new_for_files (GcalContext *context, + GFile **files, + gint n_files); + +G_END_DECLS diff --git a/src/gui/importer/gcal-import-dialog.ui b/src/gui/importer/gcal-import-dialog.ui new file mode 100644 index 00000000..38e68ea6 --- /dev/null +++ b/src/gui/importer/gcal-import-dialog.ui @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GcalImportDialog" parent="HdyWindow"> + <property name="width_request">500</property> + <property name="can_focus">False</property> + <property name="border_width">0</property> + <property name="default_width">550</property> + <property name="default_height">500</property> + <property name="resizable">False</property> + <property name="type_hint">dialog</property> + <property name="modal">True</property> + <property name="destroy_with_parent">True</property> + <child> + <object class="HdyDeck"> + <property name="visible">True</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + + <child> + <object class="HdyHeaderBar" id="headerbar"> + <property name="visible">True</property> + <property name="title" translatable="yes">Import Files…</property> + <property name="show_close_button">False</property> + + <!-- Cancel button --> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Cancel</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="on_cancel_button_clicked_cb" object="GcalImportDialog" swapped="no" /> + </object> + </child> + + <!-- Import button --> + <child> + <object class="GtkButton" id="import_button"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Import</property> + <property name="use-underline">True</property> + <signal name="clicked" handler="on_import_button_clicked_cb" object="GcalImportDialog" swapped="no" /> + <style> + <class name="suggested-action" /> + </style> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + + </object> + </child> + + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hscrollbar-policy">never</property> + <property name="propagate-natural-height">True</property> + <property name="min-content-height">400</property> + <property name="max-content-height">700</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <property name="margin-top">24</property> + <property name="margin-bottom">24</property> + <property name="margin-start">36</property> + <property name="margin-end">36</property> + <property name="orientation">vertical</property> + + <!-- Calendar row --> + <child> + <object class="GtkListBox"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="on_select_calendar_row_activated_cb" object="GcalImportDialog" swapped="no" /> + <style> + <class name="content" /> + </style> + + <child> + <object class="HdyActionRow" id="calendar_row"> + <property name="visible">True</property> + <property name="activatable">True</property> + <property name="title" translatable="yes">C_alendar</property> + <property name="use-underline">True</property> + + <child> + <object class="GtkBox" id="calendar_row_widgets_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">12</property> + <property name="spacing">12</property> + + <!-- Color --> + <child> + <object class="GtkImage" id="calendar_color_image"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + </child> + + <!-- Calendar name --> + <child> + <object class="GtkLabel" id="calendar_name_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="can-focus">False</property> + </object> + </child> + + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="pixel-size">16</property> + <property name="icon-name">pan-down-symbolic</property> + </object> + </child> + + </object> + </child> + + </object> + </child> + </object> + </child> + + <!-- Files overview --> + <child> + <object class="GtkListBox" id="files_listbox"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="selection-mode">none</property> + + <child type="placeholder"> + <object class="GtkSpinner"> + <property name="visible">True</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="active">True</property> + </object> + </child> + + <style> + <class name="background" /> + </style> + </object> + </child> + + </object> + </child> + + </object> + </child> + + </object> + </child> + </object> + </child> + + </template> + + <!-- Calendars popover --> + <object class="GtkPopover" id="calendars_popover"> + <property name="position">bottom</property> + <property name="relative-to">calendar_row_widgets_box</property> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="hscrollbar-policy">never</property> + <property name="max-content-height">350</property> + <property name="propagate-natural-width">True</property> + <property name="propagate-natural-height">True</property> + <child> + <object class="GtkListBox" id="calendars_listbox"> + <property name="visible">True</property> + <property name="hexpand">True</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="on_calendars_listbox_row_activated_cb" object="GcalImportDialog" swapped="no" /> + </object> + </child> + </object> + </child> + </object> + + <object class="GtkSizeGroup" id="title_sizegroup"> + <property name="mode">horizontal</property> + </object> + +</interface> diff --git a/src/gui/importer/gcal-import-file-row.c b/src/gui/importer/gcal-import-file-row.c new file mode 100644 index 00000000..5970aac3 --- /dev/null +++ b/src/gui/importer/gcal-import-file-row.c @@ -0,0 +1,388 @@ +/* gcal-import-file-row.c + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalImportFileRow" + +#include "config.h" +#include "gcal-import-file-row.h" +#include "gcal-importer.h" +#include "gcal-utils.h" + +#include <glib/gi18n.h> + +struct _GcalImportFileRow +{ + GtkListBoxRow parent; + + GtkListBox *events_listbox; + GtkLabel *filename_label; + GtkSizeGroup *title_sizegroup; + + GCancellable *cancellable; + GFile *file; + GPtrArray *ical_components; +}; + +static void read_calendar_finished_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data); + +G_DEFINE_TYPE (GcalImportFileRow, gcal_import_file_row, GTK_TYPE_LIST_BOX_ROW) + +enum +{ + PROP_0, + PROP_FILE, + N_PROPS, +}; + +enum +{ + FILE_LOADED, + N_SIGNALS, +}; + +static guint signals[N_SIGNALS] = { 0, }; +static GParamSpec *properties[N_PROPS] = { NULL, }; + + +/* + * Auxiliary methods + */ + +static void +add_grid_row (GcalImportFileRow *self, + GtkGrid *grid, + gint row, + const gchar *title, + const gchar *value) +{ + GtkWidget *title_label; + GtkWidget *value_label; + + if (!value || g_utf8_strlen (value, -1) == 0) + return; + + title_label = g_object_new (GTK_TYPE_LABEL, + "visible", TRUE, + "label", title, + "xalign", 1.0, + "yalign", 0.0, + "ellipsize", PANGO_ELLIPSIZE_END, + "max-width-chars", 40, + NULL); + gtk_style_context_add_class (gtk_widget_get_style_context (title_label), "dim-label"); + gtk_grid_attach (grid, title_label, 0, row, 1, 1); + + gtk_size_group_add_widget (self->title_sizegroup, title_label); + + value_label = g_object_new (GTK_TYPE_LABEL, + "visible", TRUE, + "label", value, + "xalign", 0.0, + "selectable", TRUE, + "ellipsize", PANGO_ELLIPSIZE_END, + "max-width-chars", 40, + NULL); + gtk_grid_attach (grid, value_label, 1, row, 1, 1); +} + +static void +fill_grid_with_event_data (GcalImportFileRow *self, + GtkGrid *grid, + ICalComponent *ical_component) +{ + g_autofree gchar *start_string = NULL; + g_autofree gchar *description = NULL; + g_autofree gchar *end_string = NULL; + g_autoptr (GDateTime) start = NULL; + g_autoptr (GDateTime) end = NULL; + ICalTime *ical_start; + ICalTime *ical_end; + gint row = 0; + + ical_start = i_cal_component_get_dtstart (ical_component); + start = gcal_date_time_from_icaltime (ical_start); + if (i_cal_time_is_date (ical_start)) + start_string = g_date_time_format (start, "%x"); + else + start_string = g_date_time_format (start, "%x %X"); + + ical_end = i_cal_component_get_dtend (ical_component); + if (ical_end) + { + end = gcal_date_time_from_icaltime (ical_end); + if (i_cal_time_is_date (ical_end)) + end_string = g_date_time_format (end, "%x"); + else + end_string = g_date_time_format (end, "%x %X"); + } + else + { + end = g_date_time_add_days (start, 1); + if (i_cal_time_is_date (ical_start)) + end_string = g_date_time_format (end, "%x"); + else + end_string = g_date_time_format (end, "%x %X"); + } + + gcal_utils_extract_google_section (i_cal_component_get_description (ical_component), + &description, + NULL); + + add_grid_row (self, grid, row++, _("Title"), i_cal_component_get_summary (ical_component)); + add_grid_row (self, grid, row++, _("Location"), i_cal_component_get_location (ical_component)); + add_grid_row (self, grid, row++, _("Starts"), start_string); + add_grid_row (self, grid, row++, _("Ends"), end_string); + add_grid_row (self, grid, row++, _("Description"), description); + + g_clear_object (&ical_start); + g_clear_object (&ical_end); +} + +static void +add_events_to_listbox (GcalImportFileRow *self, + GPtrArray *events) +{ + guint i; + + for (i = 0; i < events->len; i++) + { + ICalComponent *ical_component; + GtkWidget *grid; + GtkWidget *row; + + ical_component = g_ptr_array_index (events, i); + + row = g_object_new (GTK_TYPE_LIST_BOX_ROW, + "visible", TRUE, + "activatable", FALSE, + NULL); + + grid = g_object_new (GTK_TYPE_GRID, + "visible", TRUE, + "row-spacing", 6, + "column-spacing", 12, + "margin-top", 18, + "margin-bottom", 18, + "margin-start", 24, + "margin-end", 24, + NULL); + fill_grid_with_event_data (self, GTK_GRID (grid), ical_component); + gtk_container_add (GTK_CONTAINER (row), grid); + + gtk_list_box_insert (self->events_listbox, row, -1); + } +} + +static GPtrArray* +filter_event_components (ICalComponent *component) +{ + g_autoptr (GPtrArray) event_components = NULL; + ICalComponent *aux; + + if (!component) + return NULL; + + event_components = g_ptr_array_new_full (20, g_object_unref); + aux = i_cal_component_get_first_real_component (component); + while (aux) + { + g_ptr_array_add (event_components, g_object_ref (aux)); + aux = i_cal_component_get_next_component (component, I_CAL_VEVENT_COMPONENT); + } + + return g_steal_pointer (&event_components); +} + +static void +setup_file (GcalImportFileRow *self) +{ + g_autofree gchar *basename = NULL; + + basename = g_file_get_basename (self->file); + gtk_label_set_label (self->filename_label, basename); + + gcal_importer_import_file (self->file, + self->cancellable, + read_calendar_finished_cb, + self); +} + + +/* + * Callbacks + */ + +static void +read_calendar_finished_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr (GPtrArray) event_components = NULL; + g_autoptr (GError) error = NULL; + g_autofree gchar *subtitle = NULL; + ICalComponent *component; + GcalImportFileRow *self; + + self = GCAL_IMPORT_FILE_ROW (user_data); + component = gcal_importer_import_file_finish (res, &error); + event_components = filter_event_components (component); + + gtk_widget_set_sensitive (GTK_WIDGET (self), !error && event_components && event_components->len > 0); + + if (error || !event_components || event_components->len == 0) + return; + + add_events_to_listbox (self, event_components); + + self->ical_components = g_ptr_array_ref (event_components); + + g_signal_emit (self, signals[FILE_LOADED], 0, event_components); +} + + +/* + * GObject overrides + */ + +static void +gcal_import_file_row_finalize (GObject *object) +{ + GcalImportFileRow *self = (GcalImportFileRow *)object; + + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + g_clear_object (&self->file); + g_clear_pointer (&self->ical_components, g_ptr_array_unref); + + G_OBJECT_CLASS (gcal_import_file_row_parent_class)->finalize (object); +} + +static void +gcal_import_file_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalImportFileRow *self = GCAL_IMPORT_FILE_ROW (object); + + switch (prop_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_import_file_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalImportFileRow *self = GCAL_IMPORT_FILE_ROW (object); + + switch (prop_id) + { + case PROP_FILE: + g_assert (self->file == NULL); + self->file = g_value_dup_object (value); + setup_file (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_import_file_row_class_init (GcalImportFileRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = gcal_import_file_row_finalize; + object_class->get_property = gcal_import_file_row_get_property; + object_class->set_property = gcal_import_file_row_set_property; + + signals[FILE_LOADED] = g_signal_new ("file-loaded", + GCAL_TYPE_IMPORT_FILE_ROW, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, + 1, + G_TYPE_PTR_ARRAY); + + properties[PROP_FILE] = g_param_spec_object ("file", + "An ICS file", + "An ICS file", + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/importer/gcal-import-file-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, GcalImportFileRow, events_listbox); + gtk_widget_class_bind_template_child (widget_class, GcalImportFileRow, filename_label); +} + +static void +gcal_import_file_row_init (GcalImportFileRow *self) +{ + self->cancellable = g_cancellable_new (); + + gtk_widget_init_template (GTK_WIDGET (self)); +} + +GtkWidget* +gcal_import_file_row_new (GFile *file, + GtkSizeGroup *title_sizegroup) +{ + GcalImportFileRow *self; + + self = g_object_new (GCAL_TYPE_IMPORT_FILE_ROW, + "file", file, + NULL); + self->title_sizegroup = title_sizegroup; + + return (GtkWidget*) self; +} + +void +gcal_import_file_row_show_filename (GcalImportFileRow *self) +{ + g_return_if_fail (GCAL_IS_IMPORT_FILE_ROW (self)); + + gtk_widget_show (GTK_WIDGET (self->filename_label)); +} + +GPtrArray* +gcal_import_file_row_get_ical_components (GcalImportFileRow *self) +{ + g_return_val_if_fail (GCAL_IS_IMPORT_FILE_ROW (self), NULL); + + return self->ical_components; +} diff --git a/src/gui/importer/gcal-import-file-row.h b/src/gui/importer/gcal-import-file-row.h new file mode 100644 index 00000000..0ebff738 --- /dev/null +++ b/src/gui/importer/gcal-import-file-row.h @@ -0,0 +1,37 @@ +/* gcal-import-file-row.h + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define GCAL_TYPE_IMPORT_FILE_ROW (gcal_import_file_row_get_type()) +G_DECLARE_FINAL_TYPE (GcalImportFileRow, gcal_import_file_row, GCAL, IMPORT_FILE_ROW, GtkListBoxRow) + +GtkWidget* gcal_import_file_row_new (GFile *file, + GtkSizeGroup *title_sizegroup); + +void gcal_import_file_row_show_filename (GcalImportFileRow *self); + +GPtrArray* gcal_import_file_row_get_ical_components (GcalImportFileRow *self); + +G_END_DECLS diff --git a/src/gui/importer/gcal-import-file-row.ui b/src/gui/importer/gcal-import-file-row.ui new file mode 100644 index 00000000..27bbd9f4 --- /dev/null +++ b/src/gui/importer/gcal-import-file-row.ui @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="GcalImportFileRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="activatable">False</property> + + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + + <!-- File name label --> + <child> + <object class="GtkLabel" id="filename_label"> + <property name="can-focus">False</property> + <property name="margin-top">12</property> + <property name="xalign">0.0</property> + <attributes> + <attribute name="weight" value="bold" /> + </attributes> + </object> + </child> + + <!-- Event preview listbox --> + <child> + <object class="GtkListBox" id="events_listbox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selection-mode">none</property> + <style> + <class name="content" /> + </style> + </object> + </child> + + </object> + </child> + + </template> +</interface> diff --git a/src/gui/importer/gcal-importer.c b/src/gui/importer/gcal-importer.c new file mode 100644 index 00000000..1aab8db3 --- /dev/null +++ b/src/gui/importer/gcal-importer.c @@ -0,0 +1,173 @@ +/* gcal-importer.c + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "gcal-importer.h" + +#include <glib/gi18n.h> + +G_DEFINE_QUARK (ICalErrorEnum, i_cal_error); + +static const gchar* +i_cal_error_enum_to_string (ICalErrorEnum ical_error) +{ + switch (ical_error) + { + case I_CAL_NO_ERROR: + return _("No error"); + + case I_CAL_BADARG_ERROR: + return _("Bad argument to function"); + + case I_CAL_NEWFAILED_ERROR: + case I_CAL_ALLOCATION_ERROR: + return _("Failed to allocate a new object in memory"); + + case I_CAL_MALFORMEDDATA_ERROR: + return _("File is malformed, invalid, or corrupted"); + + case I_CAL_PARSE_ERROR: + return _("Failed to parse the calendar contents"); + + case I_CAL_FILE_ERROR: + return _("Failed to read file"); + + case I_CAL_INTERNAL_ERROR: + case I_CAL_USAGE_ERROR: + case I_CAL_UNIMPLEMENTED_ERROR: + case I_CAL_UNKNOWN_ERROR: + default: + return _("Internal error"); + } +} + +static void +read_file_in_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + g_autoptr (GFileInfo) file_info = NULL; + g_autoptr (GError) error = NULL; + g_autofree gchar *contents = NULL; + g_autofree gchar *path = NULL; + ICalComponent *component; + ICalErrorEnum ical_error; + gsize length; + GFile *file; + + file = task_data; + file_info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_QUERY_INFO_NONE, + cancellable, + &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (g_strcmp0 (g_file_info_get_content_type (file_info), "text/calendar") != 0) + { + g_task_return_new_error (task, + G_FILE_ERROR, + G_FILE_ERROR_FAILED, + "%s", + _("File is not an iCalendar (.ics) file")); + return; + } + + path = g_file_get_path (file); + g_file_get_contents (path, &contents, &length, &error); + + if (error) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + component = i_cal_parser_parse_string (contents); + ical_error = i_cal_errno_return (); + + if (ical_error != I_CAL_NO_ERROR) + { + g_task_return_new_error (task, + I_CAL_ERROR, + ical_error, + "%s", + i_cal_error_enum_to_string (ical_error)); + return; + } + + if (!component) + { + g_task_return_new_error (task, + I_CAL_ERROR, + I_CAL_MALFORMEDDATA_ERROR, + "%s", + i_cal_error_enum_to_string (I_CAL_MALFORMEDDATA_ERROR)); + return; + } + + g_task_return_pointer (task, g_object_ref (component), g_object_unref); +} + +/** + * gcal_importer_import_file: + * @file: a #GFile + * @cancellable: (nullable): a #GCancellable + * @callback: a #GAsyncReadyCallback to execute upon completion + * @user_data: closure data for @callback + * + * Import an ICS file. + */ +void +gcal_importer_import_file (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + + g_autoptr (GTask) task = NULL; + + g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_task_data (task, g_object_ref (file), g_object_unref); + g_task_set_source_tag (task, gcal_importer_import_file); + g_task_run_in_thread (task, read_file_in_thread); +} + +/** + * gcal_importer_do_something_finish: + * @result: a #GAsyncResult provided to callback + * @error: a location for a #GError, or %NULL + * + * Returns: (nullable): an #ICalComponent + */ +ICalComponent* +gcal_importer_import_file_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE); + + return g_task_propagate_pointer (G_TASK (result), error); +} diff --git a/src/gui/importer/gcal-importer.h b/src/gui/importer/gcal-importer.h new file mode 100644 index 00000000..671b8fa4 --- /dev/null +++ b/src/gui/importer/gcal-importer.h @@ -0,0 +1,40 @@ +/* gcal-importer.h + * + * Copyright 2021 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gio/gio.h> + +#include <libecal/libecal.h> + +G_BEGIN_DECLS + +#define I_CAL_ERROR i_cal_error_quark () +GQuark i_cal_error_quark (void); + +void gcal_importer_import_file (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +ICalComponent* gcal_importer_import_file_finish (GAsyncResult *result, + GError **error); + +G_END_DECLS diff --git a/src/gui/importer/importer.gresource.xml b/src/gui/importer/importer.gresource.xml new file mode 100644 index 00000000..d770b48d --- /dev/null +++ b/src/gui/importer/importer.gresource.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/calendar/ui/gui/importer"> + <file compressed="true">gcal-import-dialog.ui</file> + <file compressed="true">gcal-import-file-row.ui</file> + </gresource> +</gresources> diff --git a/src/gui/importer/meson.build b/src/gui/importer/meson.build new file mode 100644 index 00000000..f635d37a --- /dev/null +++ b/src/gui/importer/meson.build @@ -0,0 +1,13 @@ +calendar_incs += include_directories('.') + +built_sources += gnome.compile_resources( + 'importer-resources', + 'importer.gresource.xml', + c_name: 'importer', +) + +sources += files( + 'gcal-import-dialog.c', + 'gcal-import-file-row.c', + 'gcal-importer.c', +) diff --git a/src/gui/meson.build b/src/gui/meson.build index 6a076076..d5670c0c 100644 --- a/src/gui/meson.build +++ b/src/gui/meson.build @@ -2,6 +2,7 @@ subdir('calendar-management') subdir('event-editor') subdir('gtk') subdir('icons') +subdir('importer') subdir('views') calendar_incs += include_directories('.') |