From b9848e544380c3915b68aaff38f3ddd6fa6b607a Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 3 May 2019 12:15:53 -0300 Subject: [PATCH] Introduce GcalCalendar GcalCalendar is an abstraction on top of ESource and ECalClient. Its initialization is asynchronous and failable. This gives us more flexibility when initializing and loading the calendars from EDS. --- src/gcal-calendar.c | 605 ++++++++++++++++++++++++++++++++++++++++++++ src/gcal-calendar.h | 78 ++++++ src/meson.build | 1 + 3 files changed, 684 insertions(+) create mode 100644 src/gcal-calendar.c create mode 100644 src/gcal-calendar.h diff --git a/src/gcal-calendar.c b/src/gcal-calendar.c new file mode 100644 index 00000000..e11860b0 --- /dev/null +++ b/src/gcal-calendar.c @@ -0,0 +1,605 @@ +/* gcal-calendar.c + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "GcalCalendar" + +#include "gcal-debug.h" +#include "gcal-calendar.h" + +struct _GcalCalendar +{ + GObject parent; + + GdkRGBA color; + ESource *source; + ECalClient *client; + + gboolean read_only; + + guint color_changed_handler_id; + guint name_changed_handler_id; + guint readonly_changed_handler_id; + guint visible_changed_handler_id; + gboolean initialized; +}; + +static void g_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GcalCalendar, gcal_calendar, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, g_initable_iface_init) + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, NULL)) + +G_DEFINE_QUARK (GcalCalendar, gcal_calendar_error); + +enum +{ + PROP_0, + PROP_CLIENT, + PROP_COLOR, + PROP_ID, + PROP_NAME, + PROP_READ_ONLY, + PROP_SOURCE, + PROP_VISIBLE, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + + +/* + * Auxiliary methods + */ + +static void +update_color (GcalCalendar *self) +{ + ESourceSelectable *selectable_extension; + const gchar *color; + + selectable_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR); + color = e_source_selectable_get_color (selectable_extension); + + if (!gdk_rgba_parse (&self->color, color)) + gdk_rgba_parse (&self->color, "#ffffff"); +} + + +/* + * Callbacks + */ + +static void +on_source_color_changed_cb (ESourceSelectable *source, + GParamSpec *pspec, + GcalCalendar *self) +{ + update_color (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]); +} + +static void +on_source_name_changed_cb (ESource *source, + GParamSpec *pspec, + GcalCalendar *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]); +} + +static void +on_client_readonly_changed_cb (EClient *client, + GParamSpec *pspec, + GcalCalendar *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_READ_ONLY]); +} + +static void +on_source_visible_changed_cb (ESourceSelectable *source, + GParamSpec *pspec, + GcalCalendar *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VISIBLE]); +} + + +/* + * GInitable iface + */ + +static gboolean +gcal_calendar_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + g_autoptr (EClient) client = NULL; + g_autoptr (GError) local_error = NULL; + ESourceSelectable *selectable_extension; + GcalCalendar *self; + + GCAL_ENTRY; + + self = GCAL_CALENDAR (initable); + + g_assert (!self->initialized); + self->initialized = TRUE; + + if (!e_source_has_extension (self->source, E_SOURCE_EXTENSION_CALENDAR)) + { + g_set_error (error, + GCAL_CALENDAR_ERROR, + GCAL_CALENDAR_ERROR_NOT_CALENDAR, + "ESource is not a calendar"); + GCAL_RETURN (FALSE); + } + + selectable_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR); + + update_color (self); + self->color_changed_handler_id = g_signal_connect (selectable_extension, + "notify::color", + G_CALLBACK (on_source_color_changed_cb), + self); + + self->visible_changed_handler_id = g_signal_connect (selectable_extension, + "notify::selected", + G_CALLBACK (on_source_visible_changed_cb), + self); + + self->name_changed_handler_id = g_signal_connect (self->source, + "notify::display-name", + G_CALLBACK (on_source_name_changed_cb), + self); + + /* 15s is arbitrary, but very rarely this will be reached */ + client = e_cal_client_connect_sync (self->source, + E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + 15, + cancellable, + &local_error); + + if (local_error) + { + g_propagate_error (error, local_error); + GCAL_RETURN (FALSE); + } + + g_assert (E_IS_CAL_CLIENT (client)); + self->client = E_CAL_CLIENT (client); + + self->readonly_changed_handler_id = g_signal_connect (client, + "notify::readonly", + G_CALLBACK (on_client_readonly_changed_cb), + self); + + GCAL_RETURN (TRUE); +} + +static void +g_initable_iface_init (GInitableIface *iface) +{ + iface->init = gcal_calendar_initable_init; +} + + +/* + * GObject overrides + */ + +static void +gcal_calendar_finalize (GObject *object) +{ + GcalCalendar *self = (GcalCalendar *)object; + + g_signal_handler_disconnect (self->source, self->color_changed_handler_id); + self->color_changed_handler_id = 0; + + g_signal_handler_disconnect (self->source, self->name_changed_handler_id); + self->name_changed_handler_id = 0; + + g_signal_handler_disconnect (self->client, self->readonly_changed_handler_id); + self->readonly_changed_handler_id = 0; + + g_clear_object (&self->client); + g_clear_object (&self->source); + + G_OBJECT_CLASS (gcal_calendar_parent_class)->finalize (object); +} + +static void +gcal_calendar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalCalendar *self = GCAL_CALENDAR (object); + + switch (prop_id) + { + case PROP_CLIENT: + g_value_set_object (value, self->client); + break; + + case PROP_COLOR: + g_value_set_boxed (value, &self->color); + break; + + case PROP_ID: + g_value_set_string (value, gcal_calendar_get_id (self)); + break; + + case PROP_NAME: + g_value_set_string (value, e_source_get_display_name (self->source)); + break; + + case PROP_READ_ONLY: + g_value_set_boolean (value, gcal_calendar_is_read_only (self)); + break; + + case PROP_SOURCE: + g_value_set_object (value, self->source); + break; + + case PROP_VISIBLE: + g_value_set_boolean (value, gcal_calendar_get_visible (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_calendar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalCalendar *self = GCAL_CALENDAR (object); + + switch (prop_id) + { + case PROP_CLIENT: + g_assert_not_reached (); + break; + + case PROP_COLOR: + gcal_calendar_set_color (self, g_value_get_boxed (value)); + break; + + case PROP_ID: + g_assert_not_reached (); + break; + + case PROP_NAME: + gcal_calendar_set_name (self, g_value_get_string (value)); + break; + + case PROP_READ_ONLY: + g_assert_not_reached (); + break; + + case PROP_SOURCE: + g_assert (self->source == NULL); + self->source = g_value_dup_object (value); + break; + + case PROP_VISIBLE: + gcal_calendar_set_visible (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_calendar_class_init (GcalCalendarClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gcal_calendar_finalize; + object_class->get_property = gcal_calendar_get_property; + object_class->set_property = gcal_calendar_set_property; + + properties[PROP_CLIENT] = g_param_spec_object ("client", + "Calendar client", + "Calendar client", + E_TYPE_CAL_CLIENT, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_COLOR] = g_param_spec_boxed ("color", + "Color", + "Color of the calendar", + GDK_TYPE_RGBA, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_ID] = g_param_spec_string ("id", + "Identifier", + "Unique identifier of the calendar", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_NAME] = g_param_spec_string ("name", + "Name", + "Name of the calendar", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + properties[PROP_READ_ONLY] = g_param_spec_boolean ("read-only", + "Read-only", + "Whether the calendar is read-only or not", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + properties[PROP_SOURCE] = g_param_spec_object ("source", + "Source", + "Source", + E_TYPE_SOURCE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + properties[PROP_VISIBLE] = g_param_spec_boolean ("visible", + "Visible", + "Whether the calendar is visible", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gcal_calendar_init (GcalCalendar *self) +{ +} + +/** + * gcal_calendar_new: + * @source: an #ESource + * @cancellable: (nullable): a cancellable + * @callback: + * @user_data: + * + * Asynchronously creates and initializes a #GcalCalendar + * from @source. + */ +void +gcal_calendar_new (ESource *source, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + return g_async_initable_new_async (GCAL_TYPE_CALENDAR, + G_PRIORITY_DEFAULT, + cancellable, + callback, + user_data, + "source", source, + NULL); +} + +/** + * @result: + * @error: (nullable): return location for a #GError + * + * Finishes the operation started by gcal_calendar_new(). + * + * Returns: (transfer full)(nullable): a #GcalCalendar + */ +GcalCalendar* +gcal_calendar_new_finish (GAsyncResult *result, + GError **error) +{ + g_autoptr (GObject) source_object = NULL; + g_autoptr (GObject) result_object = NULL; + + g_return_val_if_fail (G_IS_TASK (result), NULL); + g_return_val_if_fail (!error || !*error, NULL); + + source_object = g_async_result_get_source_object (result); + g_assert (source_object != NULL); + + result_object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), + result, + error); + + return (GcalCalendar*) g_steal_pointer (&result_object); +} + +/** + * gcal_calendar_get_color: + * @self: a #GcalCalendar + * + * Retrieves the color of @self. + * + * Returns: (transfer none): a #GdkRGBA + */ +const GdkRGBA* +gcal_calendar_get_color (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), NULL); + + return &self->color; +} + +/** + * gcal_calendar_set_color: + * @self: a #GcalCalendar + * @color: the new color + * + * Sets the color of @self. + */ +void +gcal_calendar_set_color (GcalCalendar *self, + const GdkRGBA *color) +{ + g_autofree gchar *color_string = NULL; + ESourceSelectable *selectable_extension; + + g_return_if_fail (GCAL_IS_CALENDAR (self)); + g_return_if_fail (color != NULL); + + if (gdk_rgba_equal (&self->color, color)) + return; + + self->color = *color; + + color_string = gdk_rgba_to_string (color); + selectable_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR); + e_source_selectable_set_color (selectable_extension, color_string); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR]); +} + +/** + * gcal_calendar_get_id: + * @self: a #GcalCalendar + * + * Retrieves the unique identifier of @self. + * + * Returns: (transfer none): a string + */ +const gchar* +gcal_calendar_get_id (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), NULL); + + return e_source_get_uid (self->source); +} + +/** + * gcal_calendar_get_name: + * @self: a #GcalCalendar + * + * Retrieves the user-visible name of @self. + * + * Returns: (transfer none)(nullable): a string + */ +const gchar* +gcal_calendar_get_name (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), NULL); + + return e_source_get_display_name (self->source); +} + +/** + * gcal_calendar_set_name: + * @self: a #GcalCalendar + * @name: new user-visible name + * + * Sets the user-visible name of @self. + */ +void +gcal_calendar_set_name (GcalCalendar *self, + const gchar *name) +{ + g_return_if_fail (GCAL_IS_CALENDAR (self)); + + e_source_set_display_name (self->source, name); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NAME]); +} + +/** + * gcal_calendar_is_read_only: + * @self: a #GcalCalendar + * + * Retrieves whether @self is read-only or not. + * + * Returns: %TRUE if @self is read-only, %FALSE otherwise + */ +gboolean +gcal_calendar_is_read_only (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), FALSE); + + return e_client_is_readonly (E_CLIENT (self->client)); +} + +/** + * gcal_calendar_get_source: + * @self: a #GcalCalendar + * + * Retrieves the #ESource that is backing @self. + * + * Returns: (transfer none): an #ESource + */ +ESource* +gcal_calendar_get_source (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), NULL); + + return self->source; +} + +/** + * gcal_calendar_get_client: + * @self: a #GcalCalendar + * + * Retrieves the #ECalClient that is backing @self. + * + * Returns: (transfer none): an #ECalClient + */ +ECalClient* +gcal_calendar_get_client (GcalCalendar *self) +{ + g_return_val_if_fail (GCAL_IS_CALENDAR (self), NULL); + + return self->client; +} + +/** + * gcal_calendar_get_visible: + * @self: a #GcalCalendar + * + * Retrieves whether @self is visible to the user or not. + * + * Returns: %TRUE if @self is visible, %FALSE otherwise + */ +gboolean +gcal_calendar_get_visible (GcalCalendar *self) +{ + ESourceSelectable *selectable_extension; + + g_return_val_if_fail (GCAL_IS_CALENDAR (self), FALSE); + + selectable_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR); + return e_source_selectable_get_selected (selectable_extension); +} + +/** + * gcal_calendar_set_visible: + * @self: a #GcalCalendar + * @visible: visibility of the calendar + * + * Sets whether @self is visible or not. + */ +void +gcal_calendar_set_visible (GcalCalendar *self, + gboolean visible) +{ + ESourceSelectable *selectable_extension; + + g_return_if_fail (GCAL_IS_CALENDAR (self)); + + selectable_extension = e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR); + e_source_selectable_set_selected (selectable_extension, visible); +} + diff --git a/src/gcal-calendar.h b/src/gcal-calendar.h new file mode 100644 index 00000000..30ce125c --- /dev/null +++ b/src/gcal-calendar.h @@ -0,0 +1,78 @@ +/* gcal-calendar.h + * + * Copyright 2019 Georges Basile Stavracas Neto + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +/** + * GcalCalendarError: + * @GCAL_CALENDAR_ERROR_NOT_CALENDAR: the #ESource is not a calendar + * + * Errors that #GcalCalendar can generate. + */ +typedef enum +{ + GCAL_CALENDAR_ERROR_NOT_CALENDAR, +} GcalCalendarError; + +#define GCAL_CALENDAR_ERROR (gcal_calendar_error_quark ()) + + +#define GCAL_TYPE_CALENDAR (gcal_calendar_get_type()) +G_DECLARE_FINAL_TYPE (GcalCalendar, gcal_calendar, GCAL, CALENDAR, GObject) + +GQuark gcal_calendar_error_quark (void); + +void gcal_calendar_new (ESource *source, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +GcalCalendar* gcal_calendar_new_finish (GAsyncResult *result, + GError **error); + +ECalClient* gcal_calendar_get_client (GcalCalendar *self); + +const GdkRGBA* gcal_calendar_get_color (GcalCalendar *self); + +void gcal_calendar_set_color (GcalCalendar *self, + const GdkRGBA *color); + +const gchar* gcal_calendar_get_id (GcalCalendar *self); + +const gchar* gcal_calendar_get_name (GcalCalendar *self); + +void gcal_calendar_set_name (GcalCalendar *self, + const gchar *name); + +gboolean gcal_calendar_is_read_only (GcalCalendar *self); + +ESource* gcal_calendar_get_source (GcalCalendar *self); + +gboolean gcal_calendar_get_visible (GcalCalendar *self); + +void gcal_calendar_set_visible (GcalCalendar *self, + gboolean visible); + +G_END_DECLS diff --git a/src/meson.build b/src/meson.build index f2142960..bf3f2ce8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -42,6 +42,7 @@ sources = files( 'weather/gcal-weather-settings.c', 'gcal-application.c', 'gcal-clock.c', + 'gcal-calendar.c', 'gcal-context.c', 'gcal-date-chooser-day.c', 'gcal-date-chooser.c',