aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-config-assistant.c
blob: 9d46764255d3db662d023441841e10323220e44d (plain) (tree)
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250






















                                                                             
                                    
 












                                                                           

                                                    












                                               





                                                     























                                                                           











































                                                                   











































































































































                                                                                
                                   




                                    

                                                                        









                                                                         
                                                             














                                                              





                                                                     
                                                                 

                                                                         
                                                                         
 




                                                                         

                                                                    
                                                                 
                                          
                                
 

                                                                           



                                      
                                                                




                                                                       
                                          



















































                                                                          
                                              


















































































































































































































































































                                                                               






                                                        





































































                                                                               






                                                        


























































                                                                             
                                     








                                                                   
                                   


                                                  
                                           









                                                                               

                                                             
                                       


                                             
                                                               
                                 
         

















                                                                               






































































































































































































































































































































































































                                                                               
/*
 * e-mail-config-assistant.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 */

#include "e-mail-config-assistant.h"

#include <config.h>
#include <glib/gi18n-lib.h>

#include <libebackend/libebackend.h>

#include <mail/e-mail-config-confirm-page.h>
#include <mail/e-mail-config-identity-page.h>
#include <mail/e-mail-config-lookup-page.h>
#include <mail/e-mail-config-provider-page.h>
#include <mail/e-mail-config-receiving-page.h>
#include <mail/e-mail-config-sending-page.h>
#include <mail/e-mail-config-summary-page.h>
#include <mail/e-mail-config-welcome-page.h>

#define E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_MAIL_CONFIG_ASSISTANT, EMailConfigAssistantPrivate))

typedef struct _AutoconfigContext AutoconfigContext;

struct _EMailConfigAssistantPrivate {
    EMailSession *session;
    ESource *identity_source;
    GPtrArray *account_sources;
    GPtrArray *transport_sources;
    EMailConfigServicePage *receiving_page;
    EMailConfigServicePage *sending_page;
    EMailConfigSummaryPage *summary_page;
    EMailConfigPage *lookup_page;
    GHashTable *visited_pages;
    gboolean auto_configure_done;
};

struct _AutoconfigContext {
    GtkAssistant *assistant;
    GCancellable *cancellable;
    GtkWidget *skip_button;  /* not referenced */
};

enum {
    PROP_0,
    PROP_ACCOUNT_BACKEND,
    PROP_ACCOUNT_SOURCE,
    PROP_IDENTITY_SOURCE,
    PROP_SESSION,
    PROP_TRANSPORT_BACKEND,
    PROP_TRANSPORT_SOURCE
};

/* XXX We implement EAlertSink but don't implement a custom submit_alert()
 *     method.  So any alert results in a pop-up message dialog, which is a
 *     fashion faux pas these days.  But it's only used when submitting the
 *     the newly-configured account fails, so should rarely be seen. */

G_DEFINE_TYPE_WITH_CODE (
    EMailConfigAssistant,
    e_mail_config_assistant,
    GTK_TYPE_ASSISTANT,
    G_IMPLEMENT_INTERFACE (
        E_TYPE_ALERT_SINK, NULL)
    G_IMPLEMENT_INTERFACE (
        E_TYPE_EXTENSIBLE, NULL))

static void
autoconfig_skip_button_clicked_cb (GtkButton *button,
                                   GCancellable *cancellable)
{
    g_cancellable_cancel (cancellable);
}

static AutoconfigContext *
autoconfig_context_new (GtkAssistant *assistant)
{
    AutoconfigContext *context;
    const gchar *text;

    context = g_slice_new0 (AutoconfigContext);
    context->assistant = g_object_ref (assistant);
    context->cancellable = g_cancellable_new ();

    /* GtkAssistant sinks the floating button reference. */
    text = _("_Skip Lookup");
    context->skip_button = gtk_button_new_with_mnemonic (text);
    gtk_assistant_add_action_widget (
        context->assistant, context->skip_button);
    gtk_widget_show (context->skip_button);

    g_signal_connect_object (
        context->skip_button, "clicked",
        G_CALLBACK (autoconfig_skip_button_clicked_cb),
        context->cancellable, 0);

    return context;
}

static void
autoconfig_context_free (AutoconfigContext *context)
{
    gtk_assistant_remove_action_widget (
        context->assistant, context->skip_button);

    g_object_unref (context->assistant);
    g_object_unref (context->cancellable);

    g_slice_free (AutoconfigContext, context);
}

static gint
mail_config_assistant_provider_compare (gconstpointer data1,
                                        gconstpointer data2)
{
    const CamelProvider *provider1 = data1;
    const CamelProvider *provider2 = data2;

    /* The "none" provider comes first. */
    if (g_strcmp0 (provider1->protocol, "none") == 0)
        return -1;
    if (g_strcmp0 (provider2->protocol, "none") == 0)
        return 1;

    /* Then sort remote providers before local providers. */
    if (provider1->flags & CAMEL_PROVIDER_IS_REMOTE) {
        if (provider2->flags & CAMEL_PROVIDER_IS_REMOTE)
            return 0;
        else
            return -1;
    } else {
        if (provider2->flags & CAMEL_PROVIDER_IS_REMOTE)
            return 1;
        else
            return 0;
    }
}

static GList *
mail_config_assistant_list_providers (void)
{
    GList *list, *link;
    GQueue trash = G_QUEUE_INIT;

    list = camel_provider_list (TRUE);
    list = g_list_sort (list, mail_config_assistant_provider_compare);

    /* Keep only providers with a "mail" or "news" domain. */

    for (link = list; link != NULL; link = g_list_next (link)) {
        CamelProvider *provider = link->data;
        gboolean mail_or_news_domain;

        mail_or_news_domain =
            (g_strcmp0 (provider->domain, "mail") == 0) ||
            (g_strcmp0 (provider->domain, "news") == 0);

        if (mail_or_news_domain)
            continue;

        g_queue_push_tail (&trash, link);
    }

    while ((link = g_queue_pop_head (&trash)) != NULL)
        list = g_list_remove_link (list, link);

    return list;
}

static void
mail_config_assistant_notify_account_backend (EMailConfigServicePage *page,
                                              GParamSpec *pspec,
                                              EMailConfigAssistant *assistant)
{
    EMailConfigServiceBackend *backend;
    EMailConfigServicePage *sending_page;
    EMailConfigServicePageClass *page_class;
    CamelProvider *provider;

    backend = e_mail_config_service_page_get_active_backend (page);

    /* The Receiving Page combo box may not have an active item. */
    if (backend == NULL)
        goto notify;

    /* The Sending Page may not have been created yet. */
    if (assistant->priv->sending_page == NULL)
        goto notify;

    provider = e_mail_config_service_backend_get_provider (backend);

    /* XXX This should never fail, but the Camel macro below does
     *     not check for NULL so better to malfunction than crash. */
    g_return_if_fail (provider != NULL);

    sending_page = assistant->priv->sending_page;
    page_class = E_MAIL_CONFIG_SERVICE_PAGE_GET_CLASS (sending_page);

    /* The Sending Page is invisible when the CamelProvider for the
     * receiving type defines both a storage and transport service.
     * This is common in CamelProviders for groupware products like
     * Microsoft Exchange and Novell GroupWise. */
    if (CAMEL_PROVIDER_IS_STORE_AND_TRANSPORT (provider)) {
        backend = e_mail_config_service_page_lookup_backend (
            sending_page, provider->protocol);
        gtk_widget_hide (GTK_WIDGET (sending_page));
    } else {
        backend = e_mail_config_service_page_lookup_backend (
            sending_page, page_class->default_backend_name);
        gtk_widget_show (GTK_WIDGET (sending_page));
    }

    e_mail_config_service_page_set_active_backend (sending_page, backend);

notify:
    g_object_freeze_notify (G_OBJECT (assistant));

    g_object_notify (G_OBJECT (assistant), "account-backend");
    g_object_notify (G_OBJECT (assistant), "account-source");

    g_object_thaw_notify (G_OBJECT (assistant));
}

static void
mail_config_assistant_notify_transport_backend (EMailConfigServicePage *page,
                                                GParamSpec *pspec,
                                                EMailConfigAssistant *assistant)
{
    g_object_freeze_notify (G_OBJECT (assistant));

    g_object_notify (G_OBJECT (assistant), "transport-backend");
    g_object_notify (G_OBJECT (assistant), "transport-source");

    g_object_thaw_notify (G_OBJECT (assistant));
}

static void
mail_config_assistant_page_changed (EMailConfigPage *page,
                                    EMailConfigAssistant *assistant)
{
    gtk_assistant_set_page_complete (
        GTK_ASSISTANT (assistant), GTK_WIDGET (page),
        e_mail_config_page_check_complete (page));
}

static void
mail_config_assistant_autoconfigure_cb (GObject *source_object,
                                        GAsyncResult *result,
                                        gpointer user_data)
{
    EMailConfigAssistantPrivate *priv;
    AutoconfigContext *context;
    EMailAutoconfig *autoconfig;
    const gchar *email_address;
    gint n_pages, ii;
    GError *error = NULL;

    context = (AutoconfigContext *) user_data;
    priv = E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE (context->assistant);

    /* Whether it works or not, we only do this once. */
    priv->auto_configure_done = TRUE;

    autoconfig = e_mail_autoconfig_finish (result, &error);

    /* We don't really care about errors, we only capture the GError
     * as a debugging aid.  If this doesn't work we simply proceed to
     * the Receiving Email page. */
    if (error != NULL) {
        gtk_assistant_next_page (context->assistant);
        g_error_free (error);
        goto exit;
    }

    g_return_if_fail (E_IS_MAIL_AUTOCONFIG (autoconfig));

    /* Autoconfiguration worked!  Feed the results to the
     * service pages and then skip to the Summary page. */

    e_mail_config_service_page_auto_configure (
        priv->receiving_page, autoconfig);

    e_mail_config_service_page_auto_configure (
        priv->sending_page, autoconfig);

    /* Add these pages to the visited pages hash table to
     * prevent calling e_mail_config_page_setup_defaults(). */

    g_hash_table_add (priv->visited_pages, priv->receiving_page);
    g_hash_table_add (priv->visited_pages, priv->sending_page);

    /* Also set the initial display name to the email address
     * given so the user can just click past the Summary page. */
    email_address = e_mail_autoconfig_get_email_address (autoconfig);
    e_source_set_display_name (priv->identity_source, email_address);

    /* Go to the next page (Receiving Email) before skipping to the
     * Summary Page to get it into GtkAssistant visited page history.
     * We want the back button to return to Receiving Email. */
    gtk_assistant_next_page (context->assistant);

    /* XXX Can't find a better way to learn the page number of
     *     the summary page.  Oh my god this API is horrible. */
    n_pages = gtk_assistant_get_n_pages (context->assistant);
    for (ii = 0; ii < n_pages; ii++) {
        GtkWidget *page;

        page = gtk_assistant_get_nth_page (context->assistant, ii);
        if (E_IS_MAIL_CONFIG_SUMMARY_PAGE (page))
            break;
    }

    g_warn_if_fail (ii < n_pages);
    gtk_assistant_set_current_page (context->assistant, ii);

exit:
    /* Set the page invisible so we never revisit it. */
    gtk_widget_set_visible (GTK_WIDGET (priv->lookup_page), FALSE);

    autoconfig_context_free (context);
}

static gboolean
mail_config_assistant_provider_page_visible (GBinding *binding,
                                             const GValue *source_value,
                                             GValue *target_value,
                                             gpointer unused)
{
    EMailConfigServiceBackend *active_backend;
    EMailConfigServiceBackend *page_backend;
    EMailConfigProviderPage *page;
    GObject *target_object;
    gboolean visible;

    target_object = g_binding_get_target (binding);
    page = E_MAIL_CONFIG_PROVIDER_PAGE (target_object);
    page_backend = e_mail_config_provider_page_get_backend (page);

    active_backend = g_value_get_object (source_value);
    visible = (page_backend == active_backend);
    g_value_set_boolean (target_value, visible);

    return TRUE;
}

static void
mail_config_assistant_close_cb (GObject *object,
                                GAsyncResult *result,
                                gpointer user_data)
{
    EMailConfigAssistant *assistant;
    GdkWindow *gdk_window;
    GError *error = NULL;

    assistant = E_MAIL_CONFIG_ASSISTANT (object);

    /* Set the cursor back to normal. */
    gdk_window = gtk_widget_get_window (GTK_WIDGET (assistant));
    gdk_window_set_cursor (gdk_window, NULL);

    /* Allow user interaction with window content. */
    gtk_widget_set_sensitive (GTK_WIDGET (assistant), TRUE);

    e_mail_config_assistant_commit_finish (assistant, result, &error);

    /* Ignore cancellations. */
    if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
        g_error_free (error);

    } else if (error != NULL) {
        e_alert_submit (
            E_ALERT_SINK (assistant),
            "system:simple-error",
            error->message, NULL);
        g_error_free (error);

    } else {
        gtk_widget_destroy (GTK_WIDGET (assistant));
    }
}

static void
mail_config_assistant_set_session (EMailConfigAssistant *assistant,
                                   EMailSession *session)
{
    g_return_if_fail (E_IS_MAIL_SESSION (session));
    g_return_if_fail (assistant->priv->session == NULL);

    assistant->priv->session = g_object_ref (session);
}

static void
mail_config_assistant_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_SESSION:
            mail_config_assistant_set_session (
                E_MAIL_CONFIG_ASSISTANT (object),
                g_value_get_object (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_config_assistant_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_ACCOUNT_BACKEND:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_account_backend (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;

        case PROP_ACCOUNT_SOURCE:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_account_source (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;

        case PROP_IDENTITY_SOURCE:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_identity_source (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;

        case PROP_SESSION:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_session (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;

        case PROP_TRANSPORT_BACKEND:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_transport_backend (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;

        case PROP_TRANSPORT_SOURCE:
            g_value_set_object (
                value,
                e_mail_config_assistant_get_transport_source (
                E_MAIL_CONFIG_ASSISTANT (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
mail_config_assistant_dispose (GObject *object)
{
    EMailConfigAssistantPrivate *priv;

    priv = E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE (object);

    if (priv->session != NULL) {
        g_object_unref (priv->session);
        priv->session = NULL;
    }

    if (priv->identity_source != NULL) {
        g_object_unref (priv->identity_source);
        priv->identity_source = NULL;
    }

    if (priv->receiving_page != NULL) {
        g_object_unref (priv->receiving_page);
        priv->receiving_page = NULL;
    }

    if (priv->sending_page != NULL) {
        g_object_unref (priv->sending_page);
        priv->sending_page = NULL;
    }

    if (priv->summary_page != NULL) {
        g_object_unref (priv->summary_page);
        priv->summary_page = NULL;
    }

    if (priv->lookup_page != NULL) {
        g_object_unref (priv->lookup_page);
        priv->lookup_page = NULL;
    }

    g_ptr_array_set_size (priv->account_sources, 0);
    g_ptr_array_set_size (priv->transport_sources, 0);

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_mail_config_assistant_parent_class)->
        dispose (object);
}

static void
mail_config_assistant_finalize (GObject *object)
{
    EMailConfigAssistantPrivate *priv;

    priv = E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE (object);

    g_ptr_array_free (priv->account_sources, TRUE);
    g_ptr_array_free (priv->transport_sources, TRUE);

    g_hash_table_destroy (priv->visited_pages);

    /* Chain up to parent's finalize() method. */
    G_OBJECT_CLASS (e_mail_config_assistant_parent_class)->
        finalize (object);
}

static void
mail_config_assistant_constructed (GObject *object)
{
    EMailConfigAssistant *assistant;
    ESource *identity_source;
    ESourceRegistry *registry;
    ESourceExtension *extension;
    ESourceMailComposition *mail_composition_extension;
    ESourceMailIdentity *mail_identity_extension;
    ESourceMailSubmission *mail_submission_extension;
    EMailSession *session;
    EMailConfigPage *page;
    GList *list, *link;
    const gchar *extension_name;
    const gchar *title;

    assistant = E_MAIL_CONFIG_ASSISTANT (object);

    /* Chain up to parent's constructed() method. */
    G_OBJECT_CLASS (e_mail_config_assistant_parent_class)->
        constructed (object);

    title = _("Evolution Account Assistant");
    gtk_window_set_title (GTK_WINDOW (assistant), title);
    gtk_window_set_position (GTK_WINDOW (assistant), GTK_WIN_POS_CENTER);

    session = e_mail_config_assistant_get_session (assistant);
    registry = e_mail_session_get_registry (session);

    /* Configure a new identity source. */

    identity_source = e_source_new (NULL, NULL, NULL);
    assistant->priv->identity_source = identity_source;
    session = e_mail_config_assistant_get_session (assistant);

    extension_name = E_SOURCE_EXTENSION_MAIL_COMPOSITION;
    extension = e_source_get_extension (identity_source, extension_name);
    mail_composition_extension = E_SOURCE_MAIL_COMPOSITION (extension);

    extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
    extension = e_source_get_extension (identity_source, extension_name);
    mail_identity_extension = E_SOURCE_MAIL_IDENTITY (extension);

    extension_name = E_SOURCE_EXTENSION_MAIL_SUBMISSION;
    extension = e_source_get_extension (identity_source, extension_name);
    mail_submission_extension = E_SOURCE_MAIL_SUBMISSION (extension);

    e_source_mail_composition_set_drafts_folder (
        mail_composition_extension,
        e_mail_session_get_local_folder_uri (
        session, E_MAIL_LOCAL_FOLDER_DRAFTS));

    e_source_mail_composition_set_templates_folder (
        mail_composition_extension,
        e_mail_session_get_local_folder_uri (
        session, E_MAIL_LOCAL_FOLDER_TEMPLATES));

    e_source_mail_submission_set_sent_folder (
        mail_submission_extension,
        e_mail_session_get_local_folder_uri (
        session, E_MAIL_LOCAL_FOLDER_SENT));

    /*** Welcome Page ***/

    page = e_mail_config_welcome_page_new ();
    e_mail_config_assistant_add_page (assistant, page);

    /*** Identity Page ***/

    page = e_mail_config_identity_page_new (registry, identity_source);
    e_mail_config_identity_page_set_show_account_info (
        E_MAIL_CONFIG_IDENTITY_PAGE (page), FALSE);
    e_mail_config_identity_page_set_show_signatures (
        E_MAIL_CONFIG_IDENTITY_PAGE (page), FALSE);
    e_mail_config_assistant_add_page (assistant, page);

    /*** Lookup Page ***/

    page = e_mail_config_lookup_page_new ();
    e_mail_config_assistant_add_page (assistant, page);
    assistant->priv->lookup_page = g_object_ref (page);

    /*** Receiving Page ***/

    page = e_mail_config_receiving_page_new (registry);
    e_mail_config_assistant_add_page (assistant, page);
    assistant->priv->receiving_page = g_object_ref (page);

    g_object_bind_property (
        mail_identity_extension, "address",
        page, "email-address",
        G_BINDING_SYNC_CREATE);

    g_signal_connect (
        page, "notify::active-backend",
        G_CALLBACK (mail_config_assistant_notify_account_backend),
        assistant);

    /*** Receiving Options (multiple) ***/

    /* Populate the Receiving Email page while at the same time
     * adding a Receiving Options page for each account type. */

    list = mail_config_assistant_list_providers ();

    for (link = list; link != NULL; link = g_list_next (link)) {
        EMailConfigServiceBackend *backend;
        CamelProvider *provider = link->data;
        ESourceBackend *backend_extension;
        ESource *scratch_source;
        const gchar *backend_name;

        if (provider->object_types[CAMEL_PROVIDER_STORE] == 0)
            continue;

        /* ESource uses "backend_name" and CamelProvider
         * uses "protocol", but the terms are synonymous. */
        backend_name = provider->protocol;

        scratch_source = e_source_new (NULL, NULL, NULL);
        backend_extension = e_source_get_extension (
            scratch_source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
        e_source_backend_set_backend_name (
            backend_extension, backend_name);

        /* Keep display names synchronized. */
        g_object_bind_property (
            identity_source, "display-name",
            scratch_source, "display-name",
            G_BINDING_BIDIRECTIONAL |
            G_BINDING_SYNC_CREATE);

        /* We always pass NULL for the collection argument.
         * The backend generates its own scratch collection
         * source if implements the new_collection() method. */
        backend = e_mail_config_service_page_add_scratch_source (
            assistant->priv->receiving_page, scratch_source, NULL);

        g_object_unref (scratch_source);

        page = e_mail_config_provider_page_new (backend);

        /* Note: We exclude this page if it has no options,
         *       but we don't know that until we create it. */
        if (e_mail_config_provider_page_is_empty (
                E_MAIL_CONFIG_PROVIDER_PAGE (page))) {
            g_object_unref (g_object_ref_sink (page));
            continue;
        } else {
            e_mail_config_assistant_add_page (assistant, page);
        }

        /* Each Receiving Options page is only visible when its
         * service backend is active on the Receiving Email page. */
        g_object_bind_property_full (
            assistant->priv->receiving_page, "active-backend",
            page, "visible",
            G_BINDING_SYNC_CREATE,
            mail_config_assistant_provider_page_visible,
            NULL,
            NULL, (GDestroyNotify) NULL);
    }

    g_list_free (list);

    /*** Sending Page ***/

    page = e_mail_config_sending_page_new (registry);
    e_mail_config_assistant_add_page (assistant, page);
    assistant->priv->sending_page = g_object_ref (page);

    g_object_bind_property (
        mail_identity_extension, "address",
        page, "email-address",
        G_BINDING_SYNC_CREATE);

    g_signal_connect (
        page, "notify::active-backend",
        G_CALLBACK (mail_config_assistant_notify_transport_backend),
        assistant);

    list = mail_config_assistant_list_providers ();

    for (link = list; link != NULL; link = g_list_next (link)) {
        CamelProvider *provider = link->data;
        ESourceBackend *backend_extension;
        ESource *scratch_source;
        const gchar *backend_name;

        if (provider->object_types[CAMEL_PROVIDER_TRANSPORT] == 0)
            continue;

        /* ESource uses "backend_name" and CamelProvider
         * uses "protocol", but the terms are synonymous. */
        backend_name = provider->protocol;

        scratch_source = e_source_new (NULL, NULL, NULL);
        backend_extension = e_source_get_extension (
            scratch_source, E_SOURCE_EXTENSION_MAIL_TRANSPORT);
        e_source_backend_set_backend_name (
            backend_extension, backend_name);

        /* Keep display names synchronized. */
        g_object_bind_property (
            identity_source, "display-name",
            scratch_source, "display-name",
            G_BINDING_BIDIRECTIONAL |
            G_BINDING_SYNC_CREATE);

        /* We always pass NULL for the collection argument.
         * The backend generates its own scratch collection
         * source if implements the new_collection() method. */
        e_mail_config_service_page_add_scratch_source (
            assistant->priv->sending_page, scratch_source, NULL);

        g_object_unref (scratch_source);
    }

    g_list_free (list);

    /*** Summary Page ***/

    page = e_mail_config_summary_page_new ();
    e_mail_config_assistant_add_page (assistant, page);
    assistant->priv->summary_page = g_object_ref (page);

    g_object_bind_property (
        assistant, "account-backend",
        page, "account-backend",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        assistant, "identity-source",
        page, "identity-source",
        G_BINDING_SYNC_CREATE);

    g_object_bind_property (
        assistant, "transport-backend",
        page, "transport-backend",
        G_BINDING_SYNC_CREATE);

    /*** Confirm Page ***/

    page = e_mail_config_confirm_page_new ();
    e_mail_config_assistant_add_page (assistant, page);

    e_extensible_load_extensions (E_EXTENSIBLE (assistant));
}

static void
mail_config_assistant_remove (GtkContainer *container,
                              GtkWidget *widget)
{
    if (E_IS_MAIL_CONFIG_PAGE (widget))
        g_signal_handlers_disconnect_by_func (
            widget, mail_config_assistant_page_changed,
            E_MAIL_CONFIG_ASSISTANT (container));

    /* Chain up to parent's remove() method. */
    GTK_CONTAINER_CLASS (e_mail_config_assistant_parent_class)->
        remove (container, widget);
}

static void
mail_config_assistant_prepare (GtkAssistant *assistant,
                               GtkWidget *page)
{
    EMailConfigAssistantPrivate *priv;
    gboolean first_visit = FALSE;

    priv = E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE (assistant);

    /* Only setup defaults the first time a page is visited. */
    if (!g_hash_table_contains (priv->visited_pages, page)) {
        if (E_IS_MAIL_CONFIG_PAGE (page))
            e_mail_config_page_setup_defaults (
                E_MAIL_CONFIG_PAGE (page));
        g_hash_table_add (priv->visited_pages, page);
        first_visit = TRUE;
    }

    if (E_IS_MAIL_CONFIG_LOOKUP_PAGE (page)) {
        AutoconfigContext *context;
        ESource *source;
        ESourceMailIdentity *extension;
        const gchar *email_address;
        const gchar *extension_name;

        source = priv->identity_source;
        extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
        extension = e_source_get_extension (source, extension_name);
        email_address = e_source_mail_identity_get_address (extension);

        context = autoconfig_context_new (assistant);

        e_mail_autoconfig_new (
            email_address,
            G_PRIORITY_DEFAULT,
            context->cancellable,
            mail_config_assistant_autoconfigure_cb,
            context);
    }

    if (E_IS_MAIL_CONFIG_RECEIVING_PAGE (page) && first_visit) {
        ESource *source;
        ESourceMailIdentity *extension;
        const gchar *email_address;
        const gchar *extension_name;

        /* Use the email address from the Identity Page as
         * the initial display name, so in case we have to
         * query a remote mail server, the password prompt
         * will have a more meaningful description. */

        source = priv->identity_source;
        extension_name = E_SOURCE_EXTENSION_MAIL_IDENTITY;
        extension = e_source_get_extension (source, extension_name);
        email_address = e_source_mail_identity_get_address (extension);
        e_source_set_display_name (source, email_address);
    }
}

static void
mail_config_assistant_close (GtkAssistant *assistant)
{
    GdkCursor *gdk_cursor;
    GdkWindow *gdk_window;

    /* Do not chain up.  GtkAssistant does not implement this method. */

    /* Make the cursor appear busy. */
    gdk_cursor = gdk_cursor_new (GDK_WATCH);
    gdk_window = gtk_widget_get_window (GTK_WIDGET (assistant));
    gdk_window_set_cursor (gdk_window, gdk_cursor);
    g_object_unref (gdk_cursor);

    /* Prevent user interaction with window content. */
    gtk_widget_set_sensitive (GTK_WIDGET (assistant), FALSE);

    /* XXX This operation is not cancellable. */
    e_mail_config_assistant_commit (
        E_MAIL_CONFIG_ASSISTANT (assistant),
        NULL, mail_config_assistant_close_cb, NULL);
}

static void
mail_config_assistant_cancel (GtkAssistant *assistant)
{
    /* Do not chain up.  GtkAssistant does not implement this method. */

    gtk_widget_destroy (GTK_WIDGET (assistant));
}

static void
e_mail_config_assistant_class_init (EMailConfigAssistantClass *class)
{
    GObjectClass *object_class;
    GtkContainerClass *container_class;
    GtkAssistantClass *assistant_class;

    g_type_class_add_private (class, sizeof (EMailConfigAssistantPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = mail_config_assistant_set_property;
    object_class->get_property = mail_config_assistant_get_property;
    object_class->dispose = mail_config_assistant_dispose;
    object_class->finalize = mail_config_assistant_finalize;
    object_class->constructed = mail_config_assistant_constructed;

    container_class = GTK_CONTAINER_CLASS (class);
    container_class->remove = mail_config_assistant_remove;

    assistant_class = GTK_ASSISTANT_CLASS (class);
    assistant_class->prepare = mail_config_assistant_prepare;
    assistant_class->close = mail_config_assistant_close;
    assistant_class->cancel = mail_config_assistant_cancel;

    g_object_class_install_property (
        object_class,
        PROP_ACCOUNT_BACKEND,
        g_param_spec_object (
            "account-backend",
            "Account Backend",
            "Active mail account service backend",
            E_TYPE_MAIL_CONFIG_SERVICE_BACKEND,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_ACCOUNT_SOURCE,
        g_param_spec_object (
            "account-source",
            "Account Source",
            "Mail account source being edited",
            E_TYPE_SOURCE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_IDENTITY_SOURCE,
        g_param_spec_object (
            "identity-source",
            "Identity Source",
            "Mail identity source being edited",
            E_TYPE_SOURCE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SESSION,
        g_param_spec_object (
            "session",
            "Session",
            "Mail session",
            E_TYPE_MAIL_SESSION,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_TRANSPORT_BACKEND,
        g_param_spec_object (
            "transport-backend",
            "Transport Backend",
            "Active mail transport service backend",
            E_TYPE_MAIL_CONFIG_SERVICE_BACKEND,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_TRANSPORT_SOURCE,
        g_param_spec_object (
            "transport-source",
            "Transport Source",
            "Mail transport source being edited",
            E_TYPE_SOURCE,
            G_PARAM_READABLE |
            G_PARAM_STATIC_STRINGS));
}

static void
e_mail_config_assistant_init (EMailConfigAssistant *assistant)
{
    assistant->priv = E_MAIL_CONFIG_ASSISTANT_GET_PRIVATE (assistant);

    assistant->priv->account_sources =
        g_ptr_array_new_with_free_func (
        (GDestroyNotify) g_object_unref);

    assistant->priv->transport_sources =
        g_ptr_array_new_with_free_func (
        (GDestroyNotify) g_object_unref);

    assistant->priv->visited_pages = g_hash_table_new (NULL, NULL);
}

GtkWidget *
e_mail_config_assistant_new (EMailSession *session)
{
    g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);

    return g_object_new (
        E_TYPE_MAIL_CONFIG_ASSISTANT,
        "session", session, NULL);
}

EMailSession *
e_mail_config_assistant_get_session (EMailConfigAssistant *assistant)
{
    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    return assistant->priv->session;
}

EMailConfigServiceBackend *
e_mail_config_assistant_get_account_backend (EMailConfigAssistant *assistant)
{
    EMailConfigServicePage *page;

    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    page = assistant->priv->receiving_page;

    return e_mail_config_service_page_get_active_backend (page);
}

ESource *
e_mail_config_assistant_get_account_source (EMailConfigAssistant *assistant)
{
    EMailConfigServiceBackend *backend;
    ESource *source = NULL;

    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    backend = e_mail_config_assistant_get_account_backend (assistant);

    if (backend != NULL)
        source = e_mail_config_service_backend_get_source (backend);

    return source;
}

ESource *
e_mail_config_assistant_get_identity_source (EMailConfigAssistant *assistant)
{
    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    return assistant->priv->identity_source;
}

EMailConfigServiceBackend *
e_mail_config_assistant_get_transport_backend (EMailConfigAssistant *assistant)
{
    EMailConfigServicePage *page;

    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    page = assistant->priv->sending_page;

    return e_mail_config_service_page_get_active_backend (page);
}

ESource *
e_mail_config_assistant_get_transport_source (EMailConfigAssistant *assistant)
{
    EMailConfigServiceBackend *backend;
    ESource *source = NULL;

    g_return_val_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant), NULL);

    backend = e_mail_config_assistant_get_transport_backend (assistant);

    if (backend != NULL)
        source = e_mail_config_service_backend_get_source (backend);

    return source;
}

void
e_mail_config_assistant_add_page (EMailConfigAssistant *assistant,
                                  EMailConfigPage *page)
{
    EMailConfigPageInterface *page_interface;
    GtkAssistantPageType page_type;
    GtkWidget *page_widget;
    gint n_pages, position;
    const gchar *page_title;
    gboolean complete;

    g_return_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant));
    g_return_if_fail (E_IS_MAIL_CONFIG_PAGE (page));

    page_widget = GTK_WIDGET (page);
    page_interface = E_MAIL_CONFIG_PAGE_GET_INTERFACE (page);
    page_type = page_interface->page_type;
    page_title = page_interface->title;

    /* Determine the position to insert the page. */
    n_pages = gtk_assistant_get_n_pages (GTK_ASSISTANT (assistant));
    for (position = 0; position < n_pages; position++) {
        GtkWidget *nth_page;

        nth_page = gtk_assistant_get_nth_page (
            GTK_ASSISTANT (assistant), position);
        if (e_mail_config_page_compare (page_widget, nth_page) < 0)
            break;
    }

    gtk_widget_show (page_widget);

    /* Some pages can be clicked through unchanged. */
    complete = e_mail_config_page_check_complete (page);

    gtk_assistant_insert_page (
        GTK_ASSISTANT (assistant), page_widget, position);
    gtk_assistant_set_page_type (
        GTK_ASSISTANT (assistant), page_widget, page_type);
    gtk_assistant_set_page_title (
        GTK_ASSISTANT (assistant), page_widget, page_title);
    gtk_assistant_set_page_complete (
        GTK_ASSISTANT (assistant), page_widget, complete);

    /* XXX GtkAssistant has no equivalent to GtkNotebook's
     *     "page-added" and "page-removed" signals.  Fortunately
     *     removing a page does trigger GtkContainer::remove, so
     *     we can override that method and disconnect our signal
     *     handler before chaining up.  But I don't see any way
     *     for a subclass to intercept GtkAssistant pages being
     *     added, so we have to connect our signal handler here.
     *     Not really an issue, I'm just being pedantic. */

    g_signal_connect (
        page, "changed",
        G_CALLBACK (mail_config_assistant_page_changed),
        assistant);
}

/********************* e_mail_config_assistant_commit() **********************/

static void
mail_config_assistant_commit_cb (GObject *object,
                                 GAsyncResult *result,
                                 gpointer user_data)
{
    GSimpleAsyncResult *simple;
    GError *error = NULL;

    simple = G_SIMPLE_ASYNC_RESULT (user_data);

    e_source_registry_create_sources_finish (
        E_SOURCE_REGISTRY (object), result, &error);

    if (error != NULL)
        g_simple_async_result_take_error (simple, error);

    g_simple_async_result_complete (simple);

    g_object_unref (simple);
}

void
e_mail_config_assistant_commit (EMailConfigAssistant *assistant,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
{
    EMailConfigServiceBackend *backend;
    GSimpleAsyncResult *simple;
    ESourceRegistry *registry;
    EMailSession *session;
    ESource *source;
    GQueue *queue;
    gint n_pages, ii;

    g_return_if_fail (E_IS_MAIL_CONFIG_ASSISTANT (assistant));

    session = e_mail_config_assistant_get_session (assistant);
    registry = e_mail_session_get_registry (session);

    queue = g_queue_new ();

    /* Queue the collection data source if one is defined. */
    backend = e_mail_config_assistant_get_account_backend (assistant);
    source = e_mail_config_service_backend_get_collection (backend);
    if (source != NULL)
        g_queue_push_tail (queue, g_object_ref (source));

    /* Queue the mail-related data sources for the account. */
    source = e_mail_config_assistant_get_account_source (assistant);
    if (source != NULL)
        g_queue_push_tail (queue, g_object_ref (source));
    source = e_mail_config_assistant_get_identity_source (assistant);
    if (source != NULL)
        g_queue_push_tail (queue, g_object_ref (source));
    source = e_mail_config_assistant_get_transport_source (assistant);
    if (source != NULL)
        g_queue_push_tail (queue, g_object_ref (source));

    n_pages = gtk_assistant_get_n_pages (GTK_ASSISTANT (assistant));

    /* Tell all EMailConfigPages to commit their UI state to their
     * scratch ESources and push any additional data sources on to
     * the given source queue, such as calendars or address books
     * to be bundled with the mail account. */
    for (ii = 0; ii < n_pages; ii++) {
        GtkWidget *widget;

        widget = gtk_assistant_get_nth_page (
            GTK_ASSISTANT (assistant), ii);

        if (E_IS_MAIL_CONFIG_PAGE (widget)) {
            EMailConfigPage *page;
            page = E_MAIL_CONFIG_PAGE (widget);
            e_mail_config_page_commit_changes (page, queue);
        }
    }

    simple = g_simple_async_result_new (
        G_OBJECT (assistant), callback, user_data,
        e_mail_config_assistant_commit);

    e_source_registry_create_sources (
        registry, g_queue_peek_head_link (queue),
        cancellable, mail_config_assistant_commit_cb, simple);

    g_queue_free_full (queue, (GDestroyNotify) g_object_unref);
}

gboolean
e_mail_config_assistant_commit_finish (EMailConfigAssistant *assistant,
                                       GAsyncResult *result,
                                       GError **error)
{
    GSimpleAsyncResult *simple;

    g_return_val_if_fail (
        g_simple_async_result_is_valid (
        result, G_OBJECT (assistant),
        e_mail_config_assistant_commit), FALSE);

    simple = G_SIMPLE_ASYNC_RESULT (result);

    /* Assume success unless a GError is set. */
    return !g_simple_async_result_propagate_error (simple, error);
}