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
This commit is contained in:
Georges Basile Stavracas Neto
2021-08-16 18:26:21 -03:00
parent a080d8ec0f
commit e689f18b1e
15 changed files with 1582 additions and 1 deletions
+4
View File
@@ -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
+53 -1
View File
@@ -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);
}
@@ -574,6 +608,23 @@ gcal_application_dbus_unregister (GApplication *application,
GCAL_EXIT;
}
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)
{
@@ -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);
}
+17
View File
@@ -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));
}
+4
View File
@@ -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__ */
+569
View File
@@ -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);
}
+36
View File
@@ -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
+197
View File
@@ -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>
+388
View File
@@ -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;
}
+37
View File
@@ -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
+43
View File
@@ -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>
+173
View File
@@ -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);
}
+40
View File
@@ -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
+7
View File
@@ -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>
+13
View File
@@ -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',
)
+1
View File
@@ -2,6 +2,7 @@ subdir('calendar-management')
subdir('event-editor')
subdir('gtk')
subdir('icons')
subdir('importer')
subdir('views')
calendar_incs += include_directories('.')