Files
federated-calendar/src/gcal-source-dialog.c
T
Georges Basile Stavracas Neto 9ff5e05f9c app: automatically load resources from base path
We don't have to load most of the menus we're currently loading
by using the very handy automatic resource loading.

This patch fixup so the app loads resources automagically.
2016-02-16 18:12:18 -02:00

2248 lines
73 KiB
C

/* gcal-source-dialog.c
*
* Copyright (C) 2015 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/>.
*/
#include "gcal-source-dialog.h"
#include "gcal-utils.h"
#include <glib/gi18n.h>
#include <goa/goa.h>
#include <libedataserverui/libedataserverui.h>
struct _GcalSourceDialog
{
GtkDialog parent;
GtkWidget *add_button;
GtkWidget *back_button;
GtkWidget *calendar_color_button;
GtkWidget *calendar_visible_check;
GtkWidget *cancel_button;
GtkWidget *default_check;
GtkWidget *edit_grid;
GtkWidget *headerbar;
GtkWidget *main_scrolledwindow;
GtkWidget *name_entry;
GtkWidget *notebook;
GtkWidget *remove_button;
GtkWidget *stack;
GtkWidget *web_source_grid;
/* notification */
GtkWidget *notification;
GtkWidget *notification_label;
/* edit page widgets */
GtkWidget *account_box;
GtkWidget *account_label;
GtkWidget *account_dim_label;
GtkWidget *calendar_url_button;
GtkWidget *location_dim_label;
GtkWidget *settings_button;
/* new source details */
GtkWidget *calendar_address_entry;
GtkWidget *web_sources_listbox;
GtkWidget *web_sources_revealer;
/* credentials dialog */
GtkWidget *credentials_cancel_button;
GtkWidget *credentials_connect_button;
GtkWidget *credentials_dialog;
GtkWidget *credentials_password_entry;
GtkWidget *credentials_user_entry;
gint calendar_address_id;
gint validate_url_resource_id;
gint notification_timeout_id;
/* overview widgets */
GtkWidget *add_calendar_menu_button;
GtkWidget *calendars_listbox;
GtkWidget *online_accounts_listbox;
/* stub goa rows */
GtkWidget *exchange_stub_row;
GtkWidget *google_stub_row;
GtkWidget *owncloud_stub_row;
/* flags */
GcalSourceDialogMode mode;
ESource *source;
GList *remote_sources;
ESource *removed_source;
ESource *old_default_source;
GBinding *title_bind;
gboolean prompt_password;
/* auxiliary */
GSimpleActionGroup *action_group;
/* manager */
GcalManager *manager;
};
typedef enum
{
GCAL_ACCOUNT_TYPE_EXCHANGE,
GCAL_ACCOUNT_TYPE_GOOGLE,
GCAL_ACCOUNT_TYPE_OWNCLOUD,
GCAL_ACCOUNT_TYPE_NOT_SUPPORTED
} GcalAccountType;
#define ENTRY_PROGRESS_TIMEOUT 100
static void add_button_clicked (GtkWidget *button,
gpointer user_data);
static void add_source (GcalManager *manager,
ESource *source,
gboolean enabled,
gpointer user_data);
static void action_widget_activated (GtkWidget *widget,
gpointer user_data);
static void back_button_clicked (GtkButton *button,
gpointer user_data);
static void calendar_file_selected (GtkFileChooser *button,
gpointer user_data);
static void calendar_listbox_row_activated (GtkListBox *box,
GtkListBoxRow *row,
gpointer user_data);
static gint calendar_listbox_sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data);
static void calendar_visible_check_toggled (GObject *object,
GParamSpec *pspec,
gpointer user_data);
static void cancel_button_clicked (GtkWidget *button,
gpointer user_data);
static void clear_pages (GcalSourceDialog *dialog);
static void color_set (GtkColorButton *button,
gpointer user_data);
static void default_check_toggled (GObject *object,
GParamSpec *pspec,
gpointer user_data);
static gboolean description_label_link_activated (GtkWidget *widget,
gchar *uri,
gpointer user_data);
static void display_header_func (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data);
static gboolean is_goa_source (GcalSourceDialog *dialog,
ESource *source);
static GtkWidget* make_row_from_source (GcalSourceDialog *dialog,
ESource *source);
static void name_entry_text_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data);
static void online_accounts_listbox_row_activated (GtkListBox *box,
GtkListBoxRow *row,
gpointer user_data);
static gint online_accounts_listbox_sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data);
static void online_accounts_settings_button_clicked (GtkWidget *button,
gpointer user_data);
static void on_file_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data);
static void on_local_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data);
static void on_web_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data);
static void remove_source (GcalManager *manager,
ESource *source,
gpointer user_data);
static void response_signal (GtkDialog *dialog,
gint response_id,
gpointer user_data);
static void stack_visible_child_name_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data);
static void settings_button_clicked (GtkWidget *button,
gpointer user_data);
static gboolean pulse_web_entry (GcalSourceDialog *dialog);
static void url_entry_text_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data);
static gboolean validate_url_cb (GcalSourceDialog *dialog);
static gint prompt_credentials (GcalSourceDialog *dialog,
gchar **username,
gchar **password);
static void discover_sources_cb (GObject *source,
GAsyncResult *result,
gpointer user_data);
G_DEFINE_TYPE (GcalSourceDialog, gcal_source_dialog, GTK_TYPE_DIALOG)
GActionEntry actions[] = {
{"file", on_file_activated, NULL, NULL, NULL},
{"local", on_local_activated, NULL, NULL, NULL},
{"web", on_web_activated, NULL, NULL, NULL}
};
static void
add_button_clicked (GtkWidget *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if (self->source != NULL)
{
// Commit the new source
gcal_manager_save_source (self->manager, self->source);
self->source = NULL;
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_NORMAL);
}
if (self->remote_sources != NULL)
{
GList *l;
// Commit each new remote source
for (l = self->remote_sources; l != NULL; l = l->next)
gcal_manager_save_source (self->manager, l->data);
g_list_free (self->remote_sources);
self->remote_sources = NULL;
// Go back to overview
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_NORMAL);
}
}
static void
add_source (GcalManager *manager,
ESource *source,
gboolean enabled,
gpointer user_data)
{
GcalSourceDialog *self;
GList *children, *l;
gboolean contains_source;
self = GCAL_SOURCE_DIALOG (user_data);
children = gtk_container_get_children (GTK_CONTAINER (self->calendars_listbox));
contains_source = FALSE;
for (l = children; l != NULL; l = l->next)
{
if (g_object_get_data (l->data, "source") == source)
{
contains_source = TRUE;
break;
}
}
if (!contains_source)
{
GtkWidget *row;
ESource *parent;
parent = gcal_manager_get_source (self->manager, e_source_get_parent (source));
row = make_row_from_source (GCAL_SOURCE_DIALOG (user_data), source);
g_object_set_data (G_OBJECT (row), "source", source);
if (e_source_has_extension (parent, E_SOURCE_EXTENSION_GOA))
{
ESourceGoa *goa = e_source_get_extension (parent, E_SOURCE_EXTENSION_GOA);
g_object_set_data (G_OBJECT (row), "account-id", (gpointer) e_source_goa_get_account_id (goa));
}
gtk_container_add (GTK_CONTAINER (self->calendars_listbox), row);
g_object_unref (parent);
}
g_list_free (children);
}
/**
* action_widget_activated:
* @widget: the button which emited the signal.
* @user_data: a {@link GcalSourceDialog} instance.
*
* Emit a response when action buttons
* are clicked.
*
* Returns:
*/
static void
action_widget_activated (GtkWidget *widget,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
gint response = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "response"));
self->old_default_source = NULL;
gtk_dialog_response (GTK_DIALOG (user_data), response);
}
/**
* back_button_clicked:
*
* Returns to the previous page.
*
* Returns:
*/
static void
back_button_clicked (GtkButton *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if (gtk_stack_get_visible_child (GTK_STACK (self->stack)) == self->edit_grid)
{
// Save the source before leaving
gcal_manager_save_source (self->manager, self->source);
// Release the source ref we acquired
g_clear_object (&self->source);
}
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_NORMAL);
}
static gint
calendar_listbox_sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
GcalSourceDialog *self;
ESource *source1, *source2;
gboolean is_goa1, is_goa2;
gint retval;
self = GCAL_SOURCE_DIALOG (user_data);
// first source
source1 = g_object_get_data (G_OBJECT (row1), "source");
is_goa1 = is_goa_source (GCAL_SOURCE_DIALOG (user_data), source1);
// second source
source2 = g_object_get_data (G_OBJECT (row2), "source");
is_goa2 = is_goa_source (GCAL_SOURCE_DIALOG (user_data), source2);
if (is_goa1 == is_goa2)
{
gchar *parent_name1 = NULL;
gchar *parent_name2 = NULL;
// Retrieve parent names
get_source_parent_name_color (self->manager, source1, &parent_name1, NULL);
get_source_parent_name_color (self->manager, source2, &parent_name2, NULL);
retval = g_strcmp0 (parent_name1, parent_name2);
// If they have the same parent names, compare by the source display names
if (retval == 0)
retval = g_strcmp0 (e_source_get_display_name (source1), e_source_get_display_name (source2));
g_free (parent_name1);
g_free (parent_name2);
}
else
{
// If one is a GOA account and the other isn't, make the GOA one go first
retval = is_goa1 ? -1 : 1;
}
return retval;
}
static void
calendar_visible_check_toggled (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (object)))
gcal_manager_enable_source (self->manager, self->source);
else
gcal_manager_disable_source (self->manager, self->source);
}
static void
cancel_button_clicked (GtkWidget *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
// Destroy the ongoing created source
g_clear_object (&self->source);
// Cleanup detected remote sources that weren't added
if (self->remote_sources != NULL)
{
g_list_free_full (self->remote_sources, g_object_unref);
self->remote_sources = NULL;
}
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_NORMAL);
}
/**
* clear_pages:
*
* Clear local and web pages.
*
* Returns:
*/
static void
clear_pages (GcalSourceDialog *dialog)
{
GList *list;
gtk_entry_set_text (GTK_ENTRY (dialog->calendar_address_entry), "");
gtk_widget_set_sensitive (dialog->add_button, FALSE);
// Remove discovered web sources (if any)
list = gtk_container_get_children (GTK_CONTAINER (dialog->web_sources_listbox));
g_list_free_full (list, (GDestroyNotify) gtk_widget_destroy);
gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->web_sources_revealer), FALSE);
gtk_widget_hide (dialog->web_sources_revealer);
}
static void
color_set (GtkColorButton *button,
gpointer user_data)
{
GcalSourceDialog *self;
ESourceSelectable *extension;
GdkRGBA color;
self = GCAL_SOURCE_DIALOG (user_data);
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
extension = E_SOURCE_SELECTABLE (e_source_get_extension (self->source, E_SOURCE_EXTENSION_CALENDAR));
e_source_selectable_set_color (extension, gdk_rgba_to_string (&color));
}
static void
default_check_toggled (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
/* Retrieve the current default source */
if (self->old_default_source == NULL)
{
self->old_default_source = gcal_manager_get_default_source (self->manager);
g_object_unref (self->old_default_source);
}
/**
* Keeps toggling between the
* current source and the previous
* default source.
*/
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (object)))
gcal_manager_set_default_source (self->manager, self->source);
else
gcal_manager_set_default_source (self->manager, self->old_default_source);
}
/**
* description_label_link_activated:
*
* Show GNOME Control Center when
* the label's link is pressed.
*
* Returns:
*/
static gboolean
description_label_link_activated (GtkWidget *widget,
gchar *uri,
gpointer user_data)
{
gchar *command[] = {"gnome-control-center", "online-accounts", NULL};
g_spawn_async (NULL, command, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
return TRUE;
}
/**
* display_header_func:
*
* Shows a separator before each row.
*
*/
static void
display_header_func (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data)
{
if (before != NULL)
{
GtkWidget *header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_show (header);
gtk_list_box_row_set_header (row, header);
}
}
/**
* is_goa_source:
*
* Checks whether the source comes from
* a online account.
*
* Returns: %TRUE if the source came from a GOA account
*/
static gboolean
is_goa_source (GcalSourceDialog *dialog,
ESource *source)
{
ESource *parent;
gboolean is_goa;
g_assert (source && E_IS_SOURCE (source));
parent = gcal_manager_get_source (dialog->manager, e_source_get_parent (source));
is_goa = e_source_has_extension (parent, E_SOURCE_EXTENSION_GOA);
g_object_unref (parent);
return is_goa;
}
static void
invalidate_calendar_listbox_sort (GObject *source,
GParamSpec *pspec,
gpointer user_data)
{
g_return_if_fail (GTK_IS_LIST_BOX (user_data));
gtk_list_box_invalidate_sort (GTK_LIST_BOX (user_data));
}
/**
* make_row_from_source:
*
* Create a GtkListBoxRow for a given
* ESource.
*
* Returns: (transfer full) the new row
*/
static GtkWidget*
make_row_from_source (GcalSourceDialog *dialog,
ESource *source)
{
GtkBuilder *builder;
GtkWidget *bottom_label;
GtkWidget *top_label;
GdkPixbuf *pixbuf;
GtkWidget *icon;
GtkWidget *row;
GdkRGBA color;
gchar *parent_name;
get_source_parent_name_color (dialog->manager, source, &parent_name, NULL);
builder = gtk_builder_new_from_resource ("/org/gnome/calendar/calendar-row.ui");
/*
* Since we're destroying the builder instance before adding
* the row to the listbox, it should be referenced here so
* it isn't destroyed with the GtkBuilder.
*/
row = g_object_ref (gtk_builder_get_object (builder, "row"));
/* source color icon */
get_color_name_from_source (source, &color);
pixbuf = get_circle_pixbuf_from_color (&color, 24);
icon = GTK_WIDGET (gtk_builder_get_object (builder, "icon"));
gtk_image_set_from_pixbuf (GTK_IMAGE (icon), pixbuf);
/* source name label */
top_label = GTK_WIDGET (gtk_builder_get_object (builder, "title"));
gtk_label_set_label (GTK_LABEL (top_label), e_source_get_display_name (source));
g_object_bind_property (source, "display-name", top_label, "label", G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
g_signal_connect (source, "notify::display-name", G_CALLBACK (invalidate_calendar_listbox_sort),
dialog->calendars_listbox);
/* parent source name label */
bottom_label = GTK_WIDGET (gtk_builder_get_object (builder, "subtitle"));
gtk_label_set_label (GTK_LABEL (bottom_label), parent_name);
g_object_unref (builder);
g_object_unref (pixbuf);
g_free (parent_name);
return row;
}
/**
* name_entry_text_changed:
*
* Callend when the name entry's text
* is edited. It changes the source's
* display name, but wait's for the
* calendar's ::response signal to
* commit these changes.
*
* Returns:
*/
static void
name_entry_text_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
gboolean valid = gtk_entry_get_text_length (GTK_ENTRY (object)) > 0;
gtk_widget_set_sensitive (self->back_button, valid);
gtk_widget_set_sensitive (self->add_button, valid);
if (valid)
e_source_set_display_name (self->source, gtk_entry_get_text (GTK_ENTRY (self->name_entry)));
}
static void
spawn_goa_with_args (gchar *action,
gchar *arg)
{
gchar *command[] = {"gnome-control-center", "online-accounts", action, arg, NULL};
g_spawn_async (NULL, command, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, NULL);
}
/**
* online_accounts_listbox_row_activated:
*
* Selects the online account, or add one.
*
* Returns:
*/
static void
online_accounts_listbox_row_activated (GtkListBox *box,
GtkListBoxRow *row,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if ((GtkWidget*) row == self->exchange_stub_row)
{
spawn_goa_with_args ("add", "exchange");
}
else if ((GtkWidget*) row == self->google_stub_row)
{
spawn_goa_with_args ("add", "google");
}
else if ((GtkWidget*) row == self->owncloud_stub_row)
{
spawn_goa_with_args ("add", "owncloud");
}
else
{
GoaAccount *account;
gchar *id;
account = g_object_get_data (G_OBJECT (row), "goa-account");
g_return_if_fail (GOA_IS_ACCOUNT (account));
id = goa_account_dup_id (account);
spawn_goa_with_args (id, NULL);
g_free (id);
}
}
/**
* response_signal:
*
* Save the source when the dialog
* is close.
*
* Returns:
*/
static void
response_signal (GtkDialog *dialog,
gint response_id,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (dialog);
/* save the source */
if (self->mode == GCAL_SOURCE_DIALOG_MODE_EDIT && self->source != NULL)
{
gcal_manager_save_source (self->manager, self->source);
g_clear_object (&self->source);
}
/* commit the new source; save the current page's source */
if (self->mode == GCAL_SOURCE_DIALOG_MODE_NORMAL && response_id == GTK_RESPONSE_APPLY && self->remote_sources != NULL)
{
GList *l;
// Commit each new remote source
for (l = self->remote_sources; l != NULL; l = l->next)
gcal_manager_save_source (self->manager, l->data);
g_list_free (self->remote_sources);
self->remote_sources = NULL;
}
/* Destroy the source when the operation is cancelled */
if (self->mode == GCAL_SOURCE_DIALOG_MODE_NORMAL && response_id == GTK_RESPONSE_CANCEL && self->remote_sources != NULL)
{
g_list_free_full (self->remote_sources, g_object_unref);
self->remote_sources = NULL;
}
gtk_widget_hide (GTK_WIDGET (dialog));
}
static gboolean
is_remote_source (ESource *source)
{
gboolean has_webdav, has_auth;
g_assert (E_IS_SOURCE (source));
has_webdav = e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
has_auth = e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
if (!has_webdav || !has_auth)
return FALSE;
if (has_auth)
{
ESourceAuthentication *auth;
auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
// No host is set, it's not a remote source
if (e_source_authentication_get_host (auth) == NULL)
return FALSE;
}
if (has_webdav)
{
ESourceWebdav *webdav;
webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
// No resource path specified, not a remote source
if (e_source_webdav_get_resource_path (webdav) == NULL)
return FALSE;
}
return TRUE;
}
static void
settings_button_clicked (GtkWidget *button,
gpointer user_data)
{
GcalSourceDialog *self;
const gchar *account_id;
self = GCAL_SOURCE_DIALOG (user_data);
/* Selects the account to open */
account_id = g_object_get_data (G_OBJECT (self->account_label), "account-id");
spawn_goa_with_args ((gchar*) account_id, NULL);
}
static void
stack_visible_child_name_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GcalSourceDialog *self;
GtkWidget *visible_child;
self = GCAL_SOURCE_DIALOG (user_data);
visible_child = gtk_stack_get_visible_child (GTK_STACK (object));
if (visible_child == self->main_scrolledwindow)
{
gtk_header_bar_set_title (GTK_HEADER_BAR (self->headerbar), _("Calendar Settings"));
gtk_header_bar_set_subtitle (GTK_HEADER_BAR (self->headerbar), NULL);
gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->headerbar), TRUE);
gtk_widget_set_visible (self->add_button, FALSE);
gtk_widget_set_visible (self->cancel_button, FALSE);
gtk_widget_set_visible (self->back_button, FALSE);
}
/*
* Update fields when it goes to the edit page.
* Here, only widgets that depends on the current
* source are updated, while indenpendent widgets
* are updated at #gcal_source_dialog_set_mode
*/
if (visible_child == self->edit_grid && self->source != NULL)
{
ESource *default_source;
gchar *parent_name;
GdkRGBA color;
gboolean creation_mode, is_goa, is_file, is_remote;
default_source = gcal_manager_get_default_source (self->manager);
creation_mode = (self->mode == GCAL_SOURCE_DIALOG_MODE_CREATE ||
self->mode == GCAL_SOURCE_DIALOG_MODE_CREATE_WEB);
is_goa = is_goa_source (GCAL_SOURCE_DIALOG (user_data), self->source);
is_file = e_source_has_extension (self->source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
is_remote = is_remote_source (self->source);
get_source_parent_name_color (self->manager, self->source, &parent_name, NULL);
// update headerbar buttons
gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->headerbar), !creation_mode);
gtk_widget_set_visible (self->calendar_visible_check, !creation_mode);
gtk_widget_set_visible (self->back_button, !creation_mode);
gtk_widget_set_visible (self->add_button, creation_mode);
gtk_widget_set_visible (self->cancel_button, creation_mode);
gtk_widget_set_visible (self->account_box, is_goa);
gtk_widget_set_visible (self->calendar_url_button, !is_goa && (is_file || is_remote));
// If it's a file, set the file path
if (is_file)
{
ESourceLocal *local;
GFile *file;
gchar *uri;
local = e_source_get_extension (self->source, E_SOURCE_EXTENSION_LOCAL_BACKEND);
file = e_source_local_get_custom_file (local);
uri = g_file_get_uri (file);
gtk_link_button_set_uri (GTK_LINK_BUTTON (self->calendar_url_button), uri);
gtk_button_set_label (GTK_BUTTON (self->calendar_url_button), uri);
g_free (uri);
}
// If it's remote, build the uri
if (is_remote)
{
ESourceAuthentication *auth;
ESourceWebdav *webdav;
gchar *uri;
auth = e_source_get_extension (self->source, E_SOURCE_EXTENSION_AUTHENTICATION);
webdav = e_source_get_extension (self->source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
uri = g_strdup_printf ("https://%s%s", e_source_authentication_get_host (auth),
e_source_webdav_get_resource_path (webdav));
gtk_link_button_set_uri (GTK_LINK_BUTTON (self->calendar_url_button), uri);
gtk_button_set_label (GTK_BUTTON (self->calendar_url_button), uri);
g_free (uri);
}
if (is_goa)
{
gchar *name;
get_source_parent_name_color (self->manager, self->source, &name, NULL);
gtk_label_set_label (GTK_LABEL (self->account_label), name);
}
/* block signals */
g_signal_handlers_block_by_func (self->calendar_visible_check, calendar_visible_check_toggled, user_data);
g_signal_handlers_block_by_func (self->calendar_color_button, color_set, user_data);
g_signal_handlers_block_by_func (self->name_entry, name_entry_text_changed, user_data);
/* color button */
get_color_name_from_source (self->source, &color);
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (self->calendar_color_button), &color);
/* entry */
gtk_entry_set_text (GTK_ENTRY (self->name_entry), e_source_get_display_name (self->source));
// enabled check
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->calendar_visible_check),
gcal_manager_source_enabled (self->manager, self->source));
/* default source check button */
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->default_check), (self->source == default_source));
gtk_widget_set_visible (self->default_check, gcal_manager_is_client_writable (self->manager, self->source));
/* title */
if (!creation_mode)
{
gtk_header_bar_set_title (GTK_HEADER_BAR (self->headerbar), e_source_get_display_name (self->source));
gtk_header_bar_set_subtitle (GTK_HEADER_BAR (self->headerbar), parent_name);
}
/* toggle the remove button */
gtk_widget_set_visible (self->remove_button, e_source_get_removable (self->source));
/* unblock signals */
g_signal_handlers_unblock_by_func (self->calendar_visible_check, calendar_visible_check_toggled, user_data);
g_signal_handlers_unblock_by_func (self->calendar_color_button, color_set, user_data);
g_signal_handlers_unblock_by_func (self->name_entry, name_entry_text_changed, user_data);
g_object_unref (default_source);
g_free (parent_name);
}
}
/**
* calendar_file_selected:
*
* Opens a file selector dialog and
* parse the resulting selection.
*
* Returns:
*/
static void
calendar_file_selected (GtkFileChooser *button,
gpointer user_data)
{
GcalSourceDialog *self;
ESourceExtension *ext;
ESource *source;
GFile *file;
self = GCAL_SOURCE_DIALOG (user_data);
file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (button));
if (file == NULL)
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 */
e_source_set_display_name (source, g_file_get_basename (file));
// Jump to the edit page
gcal_source_dialog_set_source (GCAL_SOURCE_DIALOG (user_data), source);
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_CREATE);
gtk_widget_set_sensitive (self->add_button, TRUE);
}
/**
* calendar_listbox_row_activated:
*
* Edits the selected calendar for the
* 'Calendars' listbox or goes to the
* calendar selection for the Online
* Accounts listbox.
*
* Returns:
*/
static void
calendar_listbox_row_activated (GtkListBox *box,
GtkListBoxRow *row,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
g_assert (row != NULL);
/*
* For non-GOA calendars, show the edit page
* directly.
*/
if (GTK_WIDGET (box) == self->calendars_listbox)
{
ESource *source = g_object_get_data (G_OBJECT (row), "source");
gcal_source_dialog_set_source (GCAL_SOURCE_DIALOG (user_data), source);
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_EDIT);
}
}
/**
* pulse_web_entry:
*
* Update the url's entry with a pulse fraction.
*
* Returns: FALSE
*/
static gboolean
pulse_web_entry (GcalSourceDialog *dialog)
{
gtk_entry_progress_pulse (GTK_ENTRY (dialog->calendar_address_entry));
dialog->calendar_address_id = g_timeout_add (ENTRY_PROGRESS_TIMEOUT, (GSourceFunc) pulse_web_entry, dialog);
return FALSE;
}
/**
* url_entry_text_changed:
*
* Performs a validation of the URL
* 1 second after the user inputs.
*
* Returns:
*/
static void
url_entry_text_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GcalSourceDialog *self;
const gchar* text;
self = GCAL_SOURCE_DIALOG (user_data);
text = gtk_entry_get_text (GTK_ENTRY (self->calendar_address_entry));
if (self->calendar_address_id != 0)
{
g_source_remove (self->calendar_address_id);
self->calendar_address_id = 0;
gtk_entry_set_progress_fraction (GTK_ENTRY (self->calendar_address_entry), 0);
}
if (self->validate_url_resource_id != 0)
{
g_source_remove (self->validate_url_resource_id);
self->validate_url_resource_id = 0;
}
if (g_utf8_strlen (text, -1) != 0)
{
// Remove any previous unreleased resource
if (self->validate_url_resource_id != 0)
g_source_remove (self->validate_url_resource_id);
/*
* At first, don't bother the user with
* the login prompt. Only prompt it when
* it fails.
*/
self->prompt_password = FALSE;
self->validate_url_resource_id = g_timeout_add (500, (GSourceFunc) validate_url_cb, user_data);
}
else
{
gtk_entry_set_progress_fraction (GTK_ENTRY (self->calendar_address_entry), 0);
}
}
/**
* online_accounts_settings_button_clicked:
*
* Spawns the GNOME Control Center app
* with Online Accounts openned.
*
* Returns:
*/
static void
online_accounts_settings_button_clicked (GtkWidget *button,
gpointer user_data)
{
gchar *command[] = {"gnome-control-center", "online-accounts", NULL};
g_spawn_async (NULL, command, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
}
static void
on_file_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
GtkWidget *dialog;
GtkFileFilter *filter;
// Dialog
dialog = gtk_file_chooser_dialog_new (_("Select a calendar file"),
GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (user_data))),
GTK_FILE_CHOOSER_ACTION_SAVE,
_("Cancel"), GTK_RESPONSE_CANCEL,
_("Open"), GTK_RESPONSE_OK,
NULL);
g_signal_connect (dialog, "file-activated", G_CALLBACK (calendar_file_selected), user_data);
// File filter
filter = gtk_file_filter_new ();
gtk_file_filter_set_name (filter, _("Calendar files"));
gtk_file_filter_add_mime_type (filter, "text/calendar");
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dialog), filter);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
}
/**
* on_local_activated:
*
* Creates a new local calendar, and let
* the user adjust the settings after.
*
* Returns:
*/
static void
on_local_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
GcalSourceDialog *self;
ESourceExtension *ext;
ESource *source;
self = GCAL_SOURCE_DIALOG (user_data);
/**
* 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");
/* update the source properties */
e_source_set_display_name (source, _("Unnamed Calendar"));
// Jump to the edit page
gcal_source_dialog_set_source (GCAL_SOURCE_DIALOG (user_data), source);
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_CREATE);
gtk_widget_set_sensitive (self->add_button, TRUE);
}
/**
* on_web_activated:
*
* Redirect to the web calendar creation
* page
*
* Returns:
*/
static void
on_web_activated (GSimpleAction *action,
GVariant *param,
gpointer user_data)
{
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_CREATE_WEB);
}
static void
calendar_address_activated (GtkEntry *entry,
gpointer user_data)
{
g_assert (GCAL_IS_SOURCE_DIALOG (user_data));
validate_url_cb (GCAL_SOURCE_DIALOG (user_data));
}
/**
* validate_url_cb:
*
* Query the given URL for possible
* calendar data.
*
* Returns:FALSE
*/
static gboolean
validate_url_cb (GcalSourceDialog *dialog)
{
ESourceAuthentication *auth;
ESourceExtension *ext;
ESourceWebdav *webdav;
ESource *source;
gchar *host, *path;
gboolean uri_valid;
dialog->validate_url_resource_id = 0;
host = path = NULL;
/**
* Remove any reminescent ESources
* cached before.
*/
if (dialog->remote_sources != NULL)
{
g_list_free_full (dialog->remote_sources, g_object_unref);
dialog->remote_sources = NULL;
}
// Remove previous results
g_list_free_full (gtk_container_get_children (GTK_CONTAINER (dialog->web_sources_listbox)),
(GDestroyNotify) gtk_widget_destroy);
gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->web_sources_revealer), FALSE);
// Clear the entry icon
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (dialog->calendar_address_entry), GTK_ENTRY_ICON_SECONDARY, NULL);
// Get the hostname and file path from the server
uri_valid = uri_get_fields (gtk_entry_get_text (GTK_ENTRY (dialog->calendar_address_entry)), NULL, &host, &path);
g_debug ("[source-dialog] host: '%s', path: '%s'", host, path);
if (host == NULL || !uri_valid)
goto out;
/**
* Create the new source and add the needed
* extensions.
*/
source = e_source_new (NULL, NULL, NULL);
e_source_set_parent (source, "webcal-stub");
ext = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "webcal");
// Authentication
auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
e_source_authentication_set_host (auth, host);
// Webdav
webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
e_source_webdav_set_resource_path (webdav, path);
/*
* If we're dealing with an absolute file path,
* there is no need to check the server for more
* sources.
*/
if (g_str_has_suffix (path, ".ics"))
{
// Set the private source so it saves at closing
dialog->remote_sources = g_list_append (dialog->remote_sources, source);
// Update buttons
gtk_widget_set_sensitive (dialog->add_button, source != NULL);
}
else
{
ENamedParameters *credentials;
// Pulse the entry while it performs the check
dialog->calendar_address_id = g_timeout_add (ENTRY_PROGRESS_TIMEOUT, (GSourceFunc) pulse_web_entry, dialog);
/*
* Try to retrieve the sources without prompting
* username and password. If we get any error,
* then it prompts and retry.
*/
credentials = e_named_parameters_new ();
if (!dialog->prompt_password)
{
g_debug ("[source-dialog] Trying to connect without credentials...");
// NULL credentials
e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_USERNAME, NULL);
e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_PASSWORD, NULL);
e_webdav_discover_sources (source, gtk_entry_get_text (GTK_ENTRY (dialog->calendar_address_entry)),
E_WEBDAV_DISCOVER_SUPPORTS_EVENTS, credentials, NULL, discover_sources_cb,
dialog);
}
else
{
gint response;
gchar *user, *password;
g_debug ("[source-dialog] No credentials failed, retrying with user credentials...");
user = password = NULL;
response = prompt_credentials (dialog, &user, &password);
/*
* User entered username and password, let's try
* with it.
*/
if (response == GTK_RESPONSE_OK)
{
// User inputted credentials
e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_USERNAME, user);
e_named_parameters_set (credentials, E_SOURCE_CREDENTIAL_PASSWORD, password);
e_webdav_discover_sources (source, gtk_entry_get_text (GTK_ENTRY (dialog->calendar_address_entry)),
E_WEBDAV_DISCOVER_SUPPORTS_EVENTS, credentials, NULL, discover_sources_cb,
dialog);
}
g_free (user);
g_free (password);
}
e_named_parameters_free (credentials);
}
out:
g_free (host);
g_free (path);
return FALSE;
}
static void
credential_button_clicked (GtkWidget *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG(user_data);
if (button == self->credentials_cancel_button)
gtk_dialog_response (GTK_DIALOG (self->credentials_dialog), GTK_RESPONSE_CANCEL);
else
gtk_dialog_response (GTK_DIALOG (self->credentials_dialog), GTK_RESPONSE_OK);
}
static void
credential_entry_activate (GtkEntry *entry,
gpointer user_data)
{
gtk_dialog_response (GTK_DIALOG (user_data), GTK_RESPONSE_OK);
}
static gint
prompt_credentials (GcalSourceDialog *dialog,
gchar **username,
gchar **password)
{
gint response;
// Cleanup last credentials
gtk_entry_set_text (GTK_ENTRY (dialog->credentials_password_entry), "");
gtk_entry_set_text (GTK_ENTRY (dialog->credentials_user_entry), "");
gtk_widget_grab_focus (dialog->credentials_user_entry);
// Show the dialog, then destroy it
response = gtk_dialog_run (GTK_DIALOG (dialog->credentials_dialog));
if (response == GTK_RESPONSE_OK)
{
if (username != NULL)
*username = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->credentials_user_entry)));
if (password != NULL)
*password = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->credentials_password_entry)));
}
gtk_widget_hide (dialog->credentials_dialog);
return response;
}
static ESource*
duplicate_source (ESource *source)
{
ESourceExtension *ext;
ESource *new_source;
g_assert (source && E_IS_SOURCE (source));
new_source = e_source_new (NULL, NULL, NULL);
e_source_set_parent (new_source, "local");
ext = e_source_get_extension (new_source, E_SOURCE_EXTENSION_CALENDAR);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "local");
// Copy Authentication data
if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION))
{
ESourceAuthentication *new_auth, *parent_auth;
parent_auth = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
new_auth = e_source_get_extension (new_source, E_SOURCE_EXTENSION_AUTHENTICATION);
e_source_authentication_set_host (new_auth, e_source_authentication_get_host (parent_auth));
e_source_authentication_set_method (new_auth, e_source_authentication_get_method (parent_auth));
e_source_authentication_set_port (new_auth, e_source_authentication_get_port (parent_auth));
e_source_authentication_set_user (new_auth, e_source_authentication_get_user (parent_auth));
e_source_authentication_set_proxy_uid (new_auth, e_source_authentication_get_proxy_uid (parent_auth));
}
// Copy Webdav data
if (e_source_has_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND))
{
ESourceWebdav *new_webdav, *parent_webdav;
parent_webdav = e_source_get_extension (source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
new_webdav = e_source_get_extension (new_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
e_source_webdav_set_display_name (new_webdav, e_source_webdav_get_display_name (parent_webdav));
e_source_webdav_set_resource_path (new_webdav, e_source_webdav_get_resource_path (parent_webdav));
e_source_webdav_set_resource_query (new_webdav, e_source_webdav_get_resource_query (parent_webdav));
e_source_webdav_set_email_address (new_webdav, e_source_webdav_get_email_address (parent_webdav));
e_source_webdav_set_ssl_trust (new_webdav, e_source_webdav_get_ssl_trust (parent_webdav));
e_source_set_parent (new_source, "webcal-stub");
e_source_backend_set_backend_name (E_SOURCE_BACKEND (ext), "webcal");
}
return new_source;
}
static void
check_activated_cb (GtkWidget *check,
GParamSpec *spec,
gpointer user_data)
{
GcalSourceDialog *self;
GtkWidget *row;
ESource *source;
g_assert (user_data && GCAL_IS_SOURCE_DIALOG (user_data));
self = GCAL_SOURCE_DIALOG (user_data);
row = gtk_widget_get_parent (check);
source = g_object_get_data (G_OBJECT (row), "source");
if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check)))
self->remote_sources = g_list_append (self->remote_sources, source);
else
self->remote_sources = g_list_remove (self->remote_sources, source);
gtk_widget_set_sensitive (self->add_button, g_list_length (self->remote_sources) > 0);
}
static void
discover_sources_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GcalSourceDialog *self;
EWebDAVDiscoveredSource *src;
GSList *discovered_sources, *user_adresses, *aux;
GError *error;
self = GCAL_SOURCE_DIALOG (user_data);
error = NULL;
// Stop the pulsing entry
if (self->calendar_address_id != 0)
{
gtk_entry_set_progress_fraction (GTK_ENTRY (self->calendar_address_entry), 0);
g_source_remove (self->calendar_address_id);
self->calendar_address_id = 0;
}
if (!e_webdav_discover_sources_finish (E_SOURCE (source), result, NULL, NULL, &discovered_sources, &user_adresses,
&error))
{
// Don't add an source with errors
gtk_widget_set_sensitive (self->add_button, FALSE);
/*
* If it's the first try and things went wrong,
* retry with the user credentials. Also, it
* checks for the error code, since we don't
* really want to retry things on unavailable
* servers.
*/
if (!self->prompt_password && error->code == 14)
{
self->prompt_password = TRUE;
validate_url_cb (GCAL_SOURCE_DIALOG (user_data));
}
else
{
g_debug ("[source-dialog] error: %s", error->message);
}
g_error_free (error);
return;
}
// Add a success icon to the entry
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->calendar_address_entry), GTK_ENTRY_ICON_SECONDARY,
"emblem-ok-symbolic");
// Remove previous results
g_list_free_full (gtk_container_get_children (GTK_CONTAINER (self->web_sources_listbox)),
(GDestroyNotify) gtk_widget_destroy);
// Show the list of calendars
gtk_revealer_set_reveal_child (GTK_REVEALER (self->web_sources_revealer), TRUE);
gtk_widget_show (self->web_sources_revealer);
/* TODO: show a list of calendars */
for (aux = discovered_sources; aux != NULL; aux = aux->next)
{
gchar *resource_path = NULL;
gboolean uri_valid;
src = aux->data;
// Get the new resource path from the uri
uri_valid = uri_get_fields (src->href, NULL, NULL, &resource_path);
if (uri_valid)
{
ESourceWebdav *webdav;
GtkWidget *row, *check;
ESource *new_source;
/* build up the new source */
new_source = duplicate_source (E_SOURCE (source));
e_source_set_display_name (E_SOURCE (new_source), src->display_name);
webdav = e_source_get_extension (new_source, E_SOURCE_EXTENSION_WEBDAV_BACKEND);
e_source_webdav_set_resource_path (webdav, resource_path);
e_source_webdav_set_display_name (webdav, src->display_name);
e_source_webdav_set_email_address (webdav, user_adresses->data);
/* create the new row */
row = gtk_list_box_row_new ();
check = gtk_check_button_new ();
gtk_button_set_label (GTK_BUTTON (check), src->display_name);
g_signal_connect (check, "notify::active", G_CALLBACK (check_activated_cb), user_data);
gtk_container_add (GTK_CONTAINER (row), check);
gtk_container_add (GTK_CONTAINER (self->web_sources_listbox), row);
g_object_set_data (G_OBJECT (row), "parent-source", source);
g_object_set_data (G_OBJECT (row), "source", new_source);
g_object_set_data_full (G_OBJECT (row), "source-url", g_strdup (src->href), g_free);
g_object_set_data_full (G_OBJECT (row), "source-color", g_strdup (src->color), g_free);
g_object_set_data_full (G_OBJECT (row), "source-display-name", g_strdup (src->display_name), g_free);
gtk_widget_show_all (row);
}
g_free (resource_path);
}
// Free things up
e_webdav_discover_free_discovered_sources (discovered_sources);
g_slist_free_full (user_adresses, g_free);
}
/**
* remove_source:
*
* Removes the given source from the source
* list.
*
* Returns:
*/
static void
remove_source (GcalManager *manager,
ESource *source,
gpointer user_data)
{
GcalSourceDialog *self;
GList *children, *aux;
self = GCAL_SOURCE_DIALOG (user_data);
children = gtk_container_get_children (GTK_CONTAINER (self->calendars_listbox));
for (aux = children; aux != NULL; aux = aux->next)
{
ESource *child_source = g_object_get_data (G_OBJECT (aux->data), "source");
if (child_source != NULL && child_source == source)
{
gtk_widget_destroy (aux->data);
break;
}
}
g_list_free (children);
}
/**
* notification_child_revealed_changed:
*
* Remove the source after the notification
* is hidden.
*
* Returns:
*/
static void
notification_child_revealed_changed (GtkWidget *notification,
GParamSpec *spec,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if (gtk_revealer_get_child_revealed (GTK_REVEALER (notification)))
return;
/* If we have any removed source, delete it */
if (self->removed_source != NULL)
{
GError *error = NULL;
/* We don't really want to remove non-removable sources */
if (!e_source_get_removable (self->removed_source))
return;
// Enable the source again to remove it's name from disabled list
gcal_manager_enable_source (self->manager, self->removed_source);
e_source_remove_sync (self->removed_source, NULL, &error);
/**
* If something goes wrong, throw
* an alert and add the source back.
*/
if (error != NULL)
{
g_warning ("[source-dialog] Error removing source: %s", error->message);
add_source (self->manager, self->removed_source,
gcal_manager_source_enabled (self->manager, self->removed_source), user_data);
gcal_manager_enable_source (self->manager, self->removed_source);
g_error_free (error);
}
}
}
/**
* undo_remove_action:
*
* Readd the removed source.
*
* Returns:
*/
static void
undo_remove_action (GtkButton *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
/* if there's any set source, unremove it */
if (self->removed_source != NULL)
{
// Enable the source before adding it again
gcal_manager_enable_source (self->manager, self->removed_source);
add_source (self->manager, self->removed_source,
gcal_manager_source_enabled (self->manager, self->removed_source), user_data);
/*
* Don't clear the pointer, since we don't
* want to erase the source at all.
*/
self->removed_source = NULL;
// Hide notification
gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification), FALSE);
}
}
/**
* hide_notification:
*
* Helper function that hides the
* notification, causing the removal
* of the source (when valid).
*
* Returns:
*/
static void
hide_notification (GcalSourceDialog *dialog,
GtkWidget *button)
{
gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->notification), FALSE);
dialog->notification_timeout_id = 0;
}
/**
* hide_notification_scheduled:
*
* Limit the ammount of time that
* the notification is shown.
*
* Returns: %FALSE
*/
static gboolean
hide_notification_scheduled (gpointer dialog)
{
hide_notification (GCAL_SOURCE_DIALOG (dialog), NULL);
return FALSE;
}
/**
* remove_button_clicked:
*
* Trigger the source removal
* logic.
*
* Returns:
*/
static void
remove_button_clicked (GtkWidget *button,
gpointer user_data)
{
GcalSourceDialog *self = GCAL_SOURCE_DIALOG (user_data);
if (self->source != NULL)
{
GList *children, *l;
gchar *str;
self->removed_source = self->source;
self->source = NULL;
children = gtk_container_get_children (GTK_CONTAINER (self->calendars_listbox));
gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification), TRUE);
// Remove the listbox entry (if any)
for (l = children; l != NULL; l = l->next)
{
if (g_object_get_data (l->data, "source") == self->removed_source)
{
gtk_widget_destroy (l->data);
break;
}
}
// Update notification label
str = g_strdup_printf (_("Calendar <b>%s</b> removed"), e_source_get_display_name (self->removed_source));
gtk_label_set_markup (GTK_LABEL (self->notification_label), str);
// Remove old notifications
if (self->notification_timeout_id != 0)
g_source_remove (self->notification_timeout_id);
self->notification_timeout_id = g_timeout_add_seconds (5, hide_notification_scheduled, user_data);
// Disable the source, so it gets hidden
gcal_manager_disable_source (self->manager, self->removed_source);
g_list_free (children);
g_free (str);
}
gcal_source_dialog_set_mode (GCAL_SOURCE_DIALOG (user_data), GCAL_SOURCE_DIALOG_MODE_NORMAL);
}
GtkWidget*
gcal_source_dialog_new (void)
{
return g_object_new (GCAL_TYPE_SOURCE_DIALOG, NULL);
}
static void
gcal_source_dialog_constructed (GObject *object)
{
GcalSourceDialog *self;
GtkBuilder *builder;
GMenuModel *menu;
self = GCAL_SOURCE_DIALOG (object);
G_OBJECT_CLASS (gcal_source_dialog_parent_class)->constructed (object);
/* widget responses */
gtk_dialog_set_default_response (GTK_DIALOG (object), GTK_RESPONSE_CANCEL);
g_object_set_data (G_OBJECT (self->remove_button), "response", GINT_TO_POINTER (GCAL_RESPONSE_REMOVE_SOURCE));
// Setup listbox header functions
gtk_list_box_set_header_func (GTK_LIST_BOX (self->calendars_listbox), display_header_func, NULL, NULL);
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->calendars_listbox), (GtkListBoxSortFunc) calendar_listbox_sort_func,
object, NULL);
gtk_list_box_set_header_func (GTK_LIST_BOX (self->online_accounts_listbox), display_header_func, NULL, NULL);
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->online_accounts_listbox), (GtkListBoxSortFunc) online_accounts_listbox_sort_func,
object, NULL);
// Action group
self->action_group = g_simple_action_group_new ();
gtk_widget_insert_action_group (GTK_WIDGET (object), "source", G_ACTION_GROUP (self->action_group));
g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), actions, G_N_ELEMENTS (actions), object);
// Load the "Add" button menu
builder = gtk_builder_new_from_resource ("/org/gnome/calendar/gtk/menus.ui");
menu = G_MENU_MODEL (gtk_builder_get_object (builder, "add-source-menu"));
gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (self->add_calendar_menu_button), menu);
g_object_unref (builder);
/* setup titlebar */
gtk_window_set_titlebar (GTK_WINDOW (object), self->headerbar);
}
static void
gcal_source_dialog_class_init (GcalSourceDialogClass *klass)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
/**
* Since we cannot guarantee that the
* type system registered ESourceLocal,
* it must be ensured at least here.
*/
g_type_ensure (E_TYPE_SOURCE_LOCAL);
object_class = G_OBJECT_CLASS (klass);
object_class->constructed = gcal_source_dialog_constructed;
widget_class = GTK_WIDGET_CLASS (klass);
/* bind things for/from the template class */
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/calendar/source-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, account_box);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, account_label);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, add_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, add_calendar_menu_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, back_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, calendar_address_entry);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, calendar_color_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, calendar_url_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, calendar_visible_check);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, calendars_listbox);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, cancel_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, credentials_cancel_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, credentials_connect_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, credentials_dialog);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, credentials_password_entry);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, credentials_user_entry);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, default_check);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, edit_grid);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, exchange_stub_row);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, google_stub_row);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, headerbar);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, location_dim_label);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, main_scrolledwindow);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, name_entry);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, notification);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, notification_label);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, online_accounts_listbox);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, owncloud_stub_row);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, remove_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, settings_button);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, stack);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, web_source_grid);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, web_sources_listbox);
gtk_widget_class_bind_template_child (widget_class, GcalSourceDialog, web_sources_revealer);
gtk_widget_class_bind_template_callback (widget_class, add_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, action_widget_activated);
gtk_widget_class_bind_template_callback (widget_class, back_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, calendar_address_activated);
gtk_widget_class_bind_template_callback (widget_class, calendar_file_selected);
gtk_widget_class_bind_template_callback (widget_class, calendar_listbox_row_activated);
gtk_widget_class_bind_template_callback (widget_class, calendar_visible_check_toggled);
gtk_widget_class_bind_template_callback (widget_class, cancel_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, credential_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, credential_entry_activate);
gtk_widget_class_bind_template_callback (widget_class, color_set);
gtk_widget_class_bind_template_callback (widget_class, default_check_toggled);
gtk_widget_class_bind_template_callback (widget_class, description_label_link_activated);
gtk_widget_class_bind_template_callback (widget_class, hide_notification);
gtk_widget_class_bind_template_callback (widget_class, name_entry_text_changed);
gtk_widget_class_bind_template_callback (widget_class, notification_child_revealed_changed);
gtk_widget_class_bind_template_callback (widget_class, online_accounts_listbox_row_activated);
gtk_widget_class_bind_template_callback (widget_class, online_accounts_settings_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, remove_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, response_signal);
gtk_widget_class_bind_template_callback (widget_class, settings_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, stack_visible_child_name_changed);
gtk_widget_class_bind_template_callback (widget_class, undo_remove_action);
gtk_widget_class_bind_template_callback (widget_class, url_entry_text_changed);
}
static void
gcal_source_dialog_init (GcalSourceDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
static GcalAccountType
get_account_type (GoaAccount *account)
{
g_return_val_if_fail (GOA_IS_ACCOUNT (account), GCAL_ACCOUNT_TYPE_NOT_SUPPORTED);
if (g_strcmp0 (goa_account_get_provider_type (account), "exchange") == 0)
return GCAL_ACCOUNT_TYPE_EXCHANGE;
if (g_strcmp0 (goa_account_get_provider_type (account), "google") == 0)
return GCAL_ACCOUNT_TYPE_GOOGLE;
if (g_strcmp0 (goa_account_get_provider_type (account), "owncloud") == 0)
return GCAL_ACCOUNT_TYPE_OWNCLOUD;
return GCAL_ACCOUNT_TYPE_NOT_SUPPORTED;
}
static gint
online_accounts_listbox_sort_func (GtkListBoxRow *row1,
GtkListBoxRow *row2,
gpointer user_data)
{
GcalAccountType t1, t2;
GoaAccount *a1 = g_object_get_data (G_OBJECT (row1), "goa-account");
GoaAccount *a2 = g_object_get_data (G_OBJECT (row2), "goa-account");
if (!a1 || !a2)
return a1 ? -1 : (a2 ? 1 : 0);
t1 = get_account_type (a1);
t2 = get_account_type (a2);
if (t1 != t2)
return t1 - t2;
return g_strcmp0 (goa_account_get_identity (a1), goa_account_get_identity (a2));
}
static void
account_calendar_disable_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
GoaAccount *account = GOA_ACCOUNT (object);
g_return_if_fail (GTK_IS_LABEL (user_data));
gtk_label_set_label (GTK_LABEL (user_data), goa_account_get_calendar_disabled (account) ? _("Off") : _("On"));
}
static void
add_goa_account (GcalSourceDialog *dialog,
GoaAccount *account)
{
GcalAccountType type;
GtkBuilder *builder;
GtkWidget *provider_label;
GtkWidget *account_label;
GtkWidget *enabled_label;
GtkWidget *row;
GtkWidget *icon;
gchar *icon_name = "goa";
type = get_account_type (account);
if (type == GCAL_ACCOUNT_TYPE_NOT_SUPPORTED)
return;
builder = gtk_builder_new_from_resource ("/org/gnome/calendar/online-account-row.ui");
row = GTK_WIDGET (gtk_builder_get_object (builder, "row"));
icon = GTK_WIDGET (gtk_builder_get_object (builder, "icon"));
provider_label = GTK_WIDGET (gtk_builder_get_object (builder, "account_provider_label"));
account_label = GTK_WIDGET (gtk_builder_get_object (builder, "account_name_label"));
enabled_label = GTK_WIDGET (gtk_builder_get_object (builder, "on_off_label"));
switch (type)
{
case GCAL_ACCOUNT_TYPE_EXCHANGE:
icon_name = "goa";
gtk_widget_hide (dialog->exchange_stub_row);
break;
case GCAL_ACCOUNT_TYPE_GOOGLE:
icon_name = "goa-account-google";
gtk_widget_hide (dialog->google_stub_row);
break;
case GCAL_ACCOUNT_TYPE_OWNCLOUD:
icon_name = "goa-account-owncloud";
gtk_widget_hide (dialog->owncloud_stub_row);
break;
case GCAL_ACCOUNT_TYPE_NOT_SUPPORTED:
g_assert_not_reached ();
}
/* update fields */
gtk_label_set_label (GTK_LABEL (provider_label), goa_account_get_provider_name (account));
gtk_label_set_label (GTK_LABEL (account_label), goa_account_get_identity (account));
gtk_label_set_label (GTK_LABEL (enabled_label),
goa_account_get_calendar_disabled (account) ? _("Off") : _("On"));
gtk_image_set_from_icon_name (GTK_IMAGE (icon), icon_name, GTK_ICON_SIZE_DIALOG);
gtk_image_set_pixel_size (GTK_IMAGE (icon), 32);
g_object_set_data (G_OBJECT (row), "goa-account", account);
g_signal_connect (account, "notify::calendar-disabled",
G_CALLBACK (account_calendar_disable_changed), enabled_label);
gtk_list_box_insert (GTK_LIST_BOX (dialog->online_accounts_listbox), row, 0);
g_object_unref (builder);
}
static void
goa_account_added_cb (GoaClient *client,
GoaObject *object,
gpointer user_data)
{
add_goa_account (GCAL_SOURCE_DIALOG (user_data), goa_object_get_account (object));
}
static void
goa_account_removed_cb (GoaClient *client,
GoaObject *object,
gpointer user_data)
{
GcalSourceDialog *self;
GoaAccount *account;
GcalAccountType type;
GList *children, *l;
gint counter = 1;
self = GCAL_SOURCE_DIALOG (user_data);
account = goa_object_get_account (object);
type = get_account_type (account);
if (type == GCAL_ACCOUNT_TYPE_NOT_SUPPORTED)
return;
children = gtk_container_get_children (GTK_CONTAINER (self->online_accounts_listbox));
for (l = children; l != NULL; l = l->next)
{
GoaAccount *row_account;
GcalAccountType row_type;
row_account = g_object_get_data (l->data, "goa-account");
row_type = row_account ? get_account_type (row_account) : GCAL_ACCOUNT_TYPE_NOT_SUPPORTED;
if (row_account == account)
{
gtk_widget_destroy (l->data);
counter--;
}
else if (type == row_type)
{
counter++;
}
}
/*
* If there's any other account with the same type,
* it should show back the stub row of the given type.
*/
if (counter == 0)
{
switch (type)
{
case GCAL_ACCOUNT_TYPE_EXCHANGE:
gtk_widget_show (self->exchange_stub_row);
break;
case GCAL_ACCOUNT_TYPE_GOOGLE:
gtk_widget_show (self->google_stub_row);
break;
case GCAL_ACCOUNT_TYPE_OWNCLOUD:
gtk_widget_show (self->owncloud_stub_row);
break;
default:
g_assert_not_reached ();
}
}
g_list_free (children);
}
static void
goa_client_ready_cb (GcalSourceDialog *dialog,
GoaClient *client,
gpointer user_data)
{
GList *accounts, *l;
g_return_if_fail (GCAL_IS_SOURCE_DIALOG (dialog));
g_return_if_fail (GOA_IS_CLIENT (client));
/* Add already fetched accounts */
accounts = goa_client_get_accounts (client);
for (l = accounts; l != NULL; l = l->next)
add_goa_account (dialog, goa_object_get_account (l->data));
/* Be ready to other accounts */
g_signal_connect (client, "account-added", G_CALLBACK (goa_account_added_cb), dialog);
g_signal_connect (client, "account-removed", G_CALLBACK (goa_account_removed_cb), dialog);
g_list_free (accounts);
}
/**
* gcal_source_dialog_set_manager:
*
* Setup the {@link GcalManager} singleton
* instance of the application.
*
* Returns:
*/
void
gcal_source_dialog_set_manager (GcalSourceDialog *dialog,
GcalManager *manager)
{
dialog->manager = manager;
/*
* If the GoaClient is already loaded, fetch the online accounts
* directly. Otherwise, wait for it to be ready.
*/
if (gcal_manager_is_goa_client_ready (manager))
{
goa_client_ready_cb (dialog, gcal_manager_get_goa_client (manager), NULL);
}
else
{
g_signal_connect_swapped (manager, "goa-client-ready", G_CALLBACK (goa_client_ready_cb), dialog);
}
if (gcal_manager_load_completed (dialog->manager))
{
GList *sources, *l;
sources = gcal_manager_get_sources_connected (dialog->manager);
for (l = sources; l != NULL; l = l->next)
add_source (dialog->manager, l->data, gcal_manager_source_enabled (dialog->manager, l->data), dialog);
}
g_signal_connect (dialog->manager, "source-added", G_CALLBACK (add_source), dialog);
g_signal_connect (dialog->manager, "source-removed", G_CALLBACK (remove_source), dialog);
}
/**
* gcal_source_dialog_set_mode:
*
* Set the source dialog mode. Creation
* mode means that a new calendar will
* be created, while edit mode means a
* calendar will be edited.
*
* Returns:
*/
void
gcal_source_dialog_set_mode (GcalSourceDialog *dialog,
GcalSourceDialogMode mode)
{
GcalSourceDialogMode previous_mode = dialog->mode;
dialog->mode = mode;
// Cleanup old data
clear_pages (dialog);
switch (mode)
{
case GCAL_SOURCE_DIALOG_MODE_CREATE:
gtk_header_bar_set_title (GTK_HEADER_BAR (dialog->headerbar), _("Add Calendar"));
gtk_header_bar_set_subtitle (GTK_HEADER_BAR (dialog->headerbar), NULL);
gtk_stack_set_visible_child (GTK_STACK (dialog->stack), dialog->edit_grid);
break;
case GCAL_SOURCE_DIALOG_MODE_CREATE_WEB:
gtk_header_bar_set_title (GTK_HEADER_BAR (dialog->headerbar), _("Add Calendar"));
gtk_header_bar_set_subtitle (GTK_HEADER_BAR (dialog->headerbar), NULL);
gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (dialog->headerbar), FALSE);
gtk_stack_set_visible_child (GTK_STACK (dialog->stack), dialog->web_source_grid);
gtk_widget_set_visible (dialog->add_button, TRUE);
gtk_widget_set_visible (dialog->cancel_button, TRUE);
break;
case GCAL_SOURCE_DIALOG_MODE_EDIT:
// Bind title
if (dialog->title_bind == NULL)
{
dialog->title_bind = g_object_bind_property (dialog->name_entry, "text", dialog->headerbar, "title",
G_BINDING_DEFAULT);
}
gtk_stack_set_visible_child (GTK_STACK (dialog->stack), dialog->edit_grid);
break;
case GCAL_SOURCE_DIALOG_MODE_NORMAL:
/* Free any bindings left behind */
g_clear_pointer (&dialog->title_bind, g_binding_unbind);
gtk_header_bar_set_title (GTK_HEADER_BAR (dialog->headerbar), _("Calendar Settings"));
gtk_header_bar_set_subtitle (GTK_HEADER_BAR (dialog->headerbar), NULL);
gtk_stack_set_visible_child (GTK_STACK (dialog->stack), dialog->main_scrolledwindow);
break;
default:
g_assert_not_reached ();
}
if (previous_mode == mode)
stack_visible_child_name_changed (G_OBJECT (dialog->stack), NULL, dialog);
}
/**
* gcal_source_dialog_set_source:
*
* Sets the source to be edited by the user.
*
* Returns:
*/
void
gcal_source_dialog_set_source (GcalSourceDialog *dialog,
ESource *source)
{
g_return_if_fail (source && E_IS_SOURCE (source));
g_set_object (&dialog->source, source);
}