Files
federated-calendar/src/gui/calendar-management/gcal-new-calendar-page.c
T
Georges Basile Stavracas Neto bdda09abb8 calendar-management: Port to AdwNavigationView
The interfacing that GcalCalendarManagementDialog had is somewhat
similar to AdwNavigationView, with pages and signaling basically
matching the same semantics.

We can save some code by switching to AdwNavigationView then, as
the heavy-lifting of page management is done there now.

The headerbar of the dialog is no more; instead, each page has its
own headerbar that automatically gets a back button.

Page activation and deactivation works essentially the same, but
uses AdwNavigationView signals to manage that.
2023-06-22 00:20:21 +00:00

656 lines
20 KiB
C

/* gcal-new-calendar-page.c
*
* Copyright 2019 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 "GcalNewCalendarPage"
#include <glib/gi18n.h>
#include "gcal-context.h"
#include "gcal-calendar-management-page.h"
#include "gcal-debug.h"
#include "gcal-file-chooser-button.h"
#include "gcal-new-calendar-page.h"
#include "gcal-source-discoverer.h"
#include "gcal-utils.h"
#define ENTRY_PROGRESS_TIMEOUT 100 // ms
typedef enum
{
ENTRY_STATE_EMPTY,
ENTRY_STATE_VALIDATING,
ENTRY_STATE_VALID,
ENTRY_STATE_INVALID
} EntryState;
struct _GcalNewCalendarPage
{
GtkBox parent;
GtkWidget *add_button;
GtkEntry *calendar_address_entry;
EntryState calendar_address_entry_state;
GcalFileChooserButton *calendar_file_chooser_button;
GtkWidget *credentials_cancel_button;
GtkWidget *credentials_connect_button;
GtkWindow *credentials_dialog;
GtkEntry *credentials_password_entry;
GtkEntry *credentials_user_entry;
GtkColorDialogButton *local_calendar_color_button;
AdwEntryRow *local_calendar_name_row;
GtkWidget *web_sources_listbox;
GtkWidget *web_sources_revealer;
guint calendar_address_id;
GPtrArray *remote_sources;
guint validate_url_resource_id;
GCancellable *cancellable;
ESource *local_source;
GcalContext *context;
};
static gboolean pulse_web_entry (gpointer data);
static void sources_discovered_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data);
static void gcal_calendar_management_page_iface_init (GcalCalendarManagementPageInterface *iface);
G_DEFINE_TYPE_WITH_CODE (GcalNewCalendarPage, gcal_new_calendar_page, GTK_TYPE_BOX,
G_IMPLEMENT_INTERFACE (GCAL_TYPE_CALENDAR_MANAGEMENT_PAGE,
gcal_calendar_management_page_iface_init))
enum
{
PROP_0,
PROP_CONTEXT,
N_PROPS
};
/*
* Auxiliary methods
*/
static gchar*
calendar_path_to_name_suggestion (GFile *file)
{
g_autofree gchar *unencoded_basename = NULL;
g_autofree gchar *basename = NULL;
gchar *ext;
guint i;
const gchar*
import_file_extensions[] = {
".ical",
".ics",
".ifb",
".icalendar",
".vcs"
};
g_return_val_if_fail (G_IS_FILE (file), NULL);
unencoded_basename = g_file_get_basename (file);
basename = g_filename_display_name (unencoded_basename);
ext = strrchr (basename, '.');
if (!ext)
return NULL;
for (i = 0; i < G_N_ELEMENTS(import_file_extensions); i++)
{
if (g_ascii_strcasecmp (import_file_extensions[i], ext) == 0)
{
*ext = '\0';
break;
}
}
g_strdelimit (basename, "-_", ' ');
return g_steal_pointer (&basename);
}
static void
update_add_button (GcalNewCalendarPage *self)
{
gboolean valid;
valid = (self->local_source != NULL || self->remote_sources != NULL) &&
self->calendar_address_entry_state != ENTRY_STATE_VALIDATING &&
self->calendar_address_entry_state != ENTRY_STATE_INVALID;
gtk_widget_set_sensitive (self->add_button, valid);
}
static void
update_local_source (GcalNewCalendarPage *self)
{
g_autofree gchar *calendar_name = NULL;
g_clear_object (&self->local_source);
calendar_name = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->local_calendar_name_row)));
calendar_name = g_strstrip (calendar_name);
if (calendar_name && g_utf8_strlen (calendar_name, -1) > 0)
{
g_autofree gchar *color_string = NULL;
ESourceExtension *ext;
const GdkRGBA *color;
ESource *source;
color = gtk_color_dialog_button_get_rgba (self->local_calendar_color_button);
color_string = gdk_rgba_to_string (color);
/* Create the new source and add the needed extensions */
source = e_source_new (NULL, NULL, NULL);
e_source_set_parent (source, "local-stub");
e_source_set_display_name (source, calendar_name);
ext = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "local");
e_source_selectable_set_color (E_SOURCE_SELECTABLE (ext), color_string);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "local");
self->local_source = source;
}
update_add_button (self);
}
static void
toggle_url_entry_pulsing (GcalNewCalendarPage *self,
gboolean pulsing)
{
gtk_entry_set_progress_fraction (self->calendar_address_entry, 0);
if (pulsing && self->calendar_address_id == 0)
self->calendar_address_id = g_timeout_add (ENTRY_PROGRESS_TIMEOUT, pulse_web_entry, self);
else if (!pulsing && self->calendar_address_id != 0)
g_clear_handle_id (&self->calendar_address_id, g_source_remove);
}
static void
update_url_entry_state (GcalNewCalendarPage *self,
EntryState state)
{
self->calendar_address_entry_state = state;
if (state == ENTRY_STATE_INVALID)
gtk_widget_add_css_class (GTK_WIDGET (self->calendar_address_entry), "error");
else
gtk_widget_remove_css_class (GTK_WIDGET (self->calendar_address_entry), "error");
update_add_button (self);
toggle_url_entry_pulsing (self, state == ENTRY_STATE_VALIDATING);
}
static void
discover_sources (GcalNewCalendarPage *self)
{
const gchar *username;
const gchar *password;
const gchar *url;
GCAL_ENTRY;
self->validate_url_resource_id = 0;
url = gtk_editable_get_text (GTK_EDITABLE (self->calendar_address_entry));
username = gtk_editable_get_text (GTK_EDITABLE (self->credentials_user_entry));
password = gtk_editable_get_text (GTK_EDITABLE (self->credentials_password_entry));
gcal_discover_sources_from_uri (url,
username,
password,
self->cancellable,
sources_discovered_cb,
self);
update_url_entry_state (self, ENTRY_STATE_VALIDATING);
GCAL_EXIT;
}
static void
hide_credentials_dialog (GcalNewCalendarPage *self)
{
gtk_editable_set_text (GTK_EDITABLE (self->credentials_user_entry), "");
gtk_editable_set_text (GTK_EDITABLE (self->credentials_password_entry), "");
gtk_window_close (self->credentials_dialog);
}
/*
* Callbacks
*/
static void
on_file_chooser_button_file_changed_cb (GcalFileChooserButton *chooser,
GParamSpec *pspec,
GcalNewCalendarPage *self)
{
g_autofree gchar *display_name = NULL;
g_autoptr (ESource) source = NULL;
g_autoptr (GFile) file = NULL;
ESourceExtension *ext;
GCAL_ENTRY;
file = gcal_file_chooser_button_get_file (chooser);
if (!file)
GCAL_RETURN ();
/* Create the new source and add the needed extensions */
source = e_source_new (NULL, NULL, NULL);
e_source_set_parent (source, "local-stub");
ext = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "local");
ext = e_source_get_extension (source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
e_source_local_set_custom_file (E_SOURCE_LOCAL (ext), file);
/* update the source properties */
display_name = calendar_path_to_name_suggestion (file);
e_source_set_display_name (source, display_name);
/* TODO: report errors */
gcal_manager_save_source (gcal_context_get_manager (self->context), source);
gcal_calendar_management_page_switch_page (GCAL_CALENDAR_MANAGEMENT_PAGE (self),
"calendars",
NULL);
gcal_file_chooser_button_set_file (self->calendar_file_chooser_button, NULL);
GCAL_EXIT;
}
static gboolean
pulse_web_entry (gpointer data)
{
GcalNewCalendarPage *self = data;
gtk_entry_progress_pulse (self->calendar_address_entry);
return G_SOURCE_CONTINUE;
}
static void
sources_discovered_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GcalNewCalendarPage *self;
g_autoptr (GPtrArray) sources = NULL;
g_autoptr (GError) error = NULL;
GCAL_ENTRY;
self = GCAL_NEW_CALENDAR_PAGE (user_data);
sources = gcal_discover_sources_from_uri_finish (result, &error);
if (error)
{
if (g_error_matches (error,
GCAL_SOURCE_DISCOVERER_ERROR,
GCAL_SOURCE_DISCOVERER_ERROR_UNAUTHORIZED))
{
GtkRoot *root;
g_debug ("Unauthorized, asking for credentials...");
root = gtk_widget_get_root (GTK_WIDGET (self));
gtk_window_set_transient_for (self->credentials_dialog, GTK_WINDOW (root));
gtk_window_present (self->credentials_dialog);
}
else
{
g_warning ("Error finding sources: %s", error->message);
update_url_entry_state (self, ENTRY_STATE_INVALID);
}
GCAL_RETURN ();
}
g_debug ("Found %u sources", sources->len);
self->remote_sources = g_steal_pointer (&sources);
update_url_entry_state (self, ENTRY_STATE_VALID);
GCAL_EXIT;
}
static gboolean
validate_url_cb (gpointer data)
{
GcalNewCalendarPage *self = data;
g_autoptr (GUri) guri = NULL;
GCAL_ENTRY;
self->validate_url_resource_id = 0;
guri = g_uri_parse (gtk_editable_get_text (GTK_EDITABLE (self->calendar_address_entry)), SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL);
if (guri != NULL && g_uri_get_scheme (guri) && g_uri_get_host (guri))
{
discover_sources (self);
}
else
{
update_url_entry_state (self, ENTRY_STATE_INVALID);
g_debug ("Invalid URL passed");
}
GCAL_RETURN (G_SOURCE_REMOVE);
}
static void
on_add_button_clicked_cb (GtkWidget *button,
GcalNewCalendarPage *self)
{
GcalManager *manager;
manager = gcal_context_get_manager (self->context);
/* Commit the new source */
if (self->local_source)
gcal_manager_save_source (manager, self->local_source);
/* Commit each new remote source */
if (self->remote_sources)
{
guint i;
for (i = 0; i < self->remote_sources->len; i++)
gcal_manager_save_source (manager, g_ptr_array_index (self->remote_sources, i));
g_clear_pointer (&self->remote_sources, g_ptr_array_unref);
}
gcal_calendar_management_page_switch_page (GCAL_CALENDAR_MANAGEMENT_PAGE (self),
"calendars",
NULL);
}
static void
on_calendar_address_activated_cb (GtkEntry *entry,
GcalNewCalendarPage *self)
{
validate_url_cb (self);
}
static void
on_credential_button_clicked_cb (GtkWidget *button,
GcalNewCalendarPage *self)
{
if (button == self->credentials_connect_button)
discover_sources (self);
else
update_url_entry_state (self, ENTRY_STATE_INVALID);
hide_credentials_dialog (self);
}
static void
on_credential_entry_activate_cb (GtkEntry *entry,
GcalNewCalendarPage *self)
{
discover_sources (self);
hide_credentials_dialog (self);
}
static void
on_url_entry_text_changed_cb (GtkEntry *entry,
GParamSpec *pspec,
GcalNewCalendarPage *self)
{
const gchar* text;
GCAL_ENTRY;
text = gtk_editable_get_text (GTK_EDITABLE (entry));
if (self->calendar_address_id != 0)
{
gtk_entry_set_progress_fraction (entry, 0);
g_clear_handle_id (&self->calendar_address_id, g_source_remove);
}
/* Cleanup previous remote sources */
g_clear_pointer (&self->remote_sources, g_ptr_array_unref);
if (self->validate_url_resource_id != 0)
g_clear_handle_id (&self->validate_url_resource_id, g_source_remove);
if (text && g_utf8_strlen (text, -1) > 0)
{
/*
* At first, don't bother the user with the login prompt. Only prompt it when
* it fails.
*/
self->validate_url_resource_id = g_timeout_add (500, validate_url_cb, self);
update_url_entry_state (self, ENTRY_STATE_VALIDATING);
}
else
{
gtk_entry_set_progress_fraction (self->calendar_address_entry, 0);
update_url_entry_state (self, ENTRY_STATE_EMPTY);
}
GCAL_EXIT;
}
static void
on_local_calendar_name_row_text_changed_cb (AdwEntryRow *entry_row,
GParamSpec *pspec,
GcalNewCalendarPage *self)
{
update_local_source (self);
}
static void
on_local_calendar_color_button_rgba_changed_cb (GtkColorChooser *chooser,
GParamSpec *pspec,
GcalNewCalendarPage *self)
{
update_local_source (self);
}
static void
on_web_description_label_link_activated_cb (GtkLabel *label,
gchar *uri,
GcalNewCalendarPage *self)
{
GDBusConnection *connection = g_application_get_dbus_connection (g_application_get_default ());
gcal_utils_launch_online_accounts_panel (connection, NULL, NULL);
}
/*
* GcalCalendarManagementPage iface
*/
static const gchar*
gcal_new_calendar_page_get_name (GcalCalendarManagementPage *page)
{
return "new-calendar";
}
static const gchar*
gcal_new_calendar_page_get_title (GcalCalendarManagementPage *page)
{
return _("New Calendar");
}
static void
gcal_new_calendar_page_deactivate (GcalCalendarManagementPage *page)
{
GcalNewCalendarPage *self;
GCAL_ENTRY;
self = GCAL_NEW_CALENDAR_PAGE (page);
g_clear_object (&self->local_source);
g_clear_pointer (&self->remote_sources, g_ptr_array_unref);
update_add_button (self);
gtk_editable_set_text (GTK_EDITABLE (self->local_calendar_name_row), "");
gtk_editable_set_text (GTK_EDITABLE (self->calendar_address_entry), "");
toggle_url_entry_pulsing (self, FALSE);
GCAL_EXIT;
}
static void
gcal_calendar_management_page_iface_init (GcalCalendarManagementPageInterface *iface)
{
iface->get_name = gcal_new_calendar_page_get_name;
iface->get_title = gcal_new_calendar_page_get_title;
iface->deactivate = gcal_new_calendar_page_deactivate;
}
/*
* GObject overrides
*/
static void
gcal_new_calendar_page_dispose (GObject *object)
{
GcalNewCalendarPage *self = (GcalNewCalendarPage *)object;
gtk_widget_dispose_template (GTK_WIDGET (self), GCAL_TYPE_NEW_CALENDAR_PAGE);
G_OBJECT_CLASS (gcal_new_calendar_page_parent_class)->dispose (object);
}
static void
gcal_new_calendar_page_finalize (GObject *object)
{
GcalNewCalendarPage *self = (GcalNewCalendarPage *)object;
g_clear_handle_id (&self->calendar_address_id, g_source_remove);
g_clear_handle_id (&self->validate_url_resource_id, g_source_remove);
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
g_clear_object (&self->context);
G_OBJECT_CLASS (gcal_new_calendar_page_parent_class)->finalize (object);
}
static void
gcal_new_calendar_page_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GcalNewCalendarPage *self = GCAL_NEW_CALENDAR_PAGE (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_new_calendar_page_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GcalNewCalendarPage *self = GCAL_NEW_CALENDAR_PAGE (object);
switch (prop_id)
{
case PROP_CONTEXT:
self->context = g_value_dup_object (value);
g_assert (self->context != NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
gcal_new_calendar_page_class_init (GcalNewCalendarPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = gcal_new_calendar_page_dispose;
object_class->finalize = gcal_new_calendar_page_finalize;
object_class->get_property = gcal_new_calendar_page_get_property;
object_class->set_property = gcal_new_calendar_page_set_property;
g_object_class_override_property (object_class, PROP_CONTEXT, "context");
g_type_ensure (GCAL_TYPE_FILE_CHOOSER_BUTTON);
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/ui/gui/calendar-management/gcal-new-calendar-page.ui");
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, add_button);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, calendar_address_entry);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, calendar_file_chooser_button);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, credentials_cancel_button);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, credentials_connect_button);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, credentials_dialog);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, credentials_password_entry);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, credentials_user_entry);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, local_calendar_color_button);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, local_calendar_name_row);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, web_sources_listbox);
gtk_widget_class_bind_template_child (widget_class, GcalNewCalendarPage, web_sources_revealer);
gtk_widget_class_bind_template_callback (widget_class, on_add_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, on_calendar_address_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, on_credential_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, on_credential_entry_activate_cb);
gtk_widget_class_bind_template_callback (widget_class, on_file_chooser_button_file_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_local_calendar_name_row_text_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_local_calendar_color_button_rgba_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_url_entry_text_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_web_description_label_link_activated_cb);
}
static void
gcal_new_calendar_page_init (GcalNewCalendarPage *self)
{
self->cancellable = g_cancellable_new ();
gtk_widget_init_template (GTK_WIDGET (self));
}