aboutsummaryrefslogblamecommitdiffstats
path: root/mail/e-mail-config-assistant.c
blob: e1a4add3eda5e022b6c68b73e56af0ecd4facb50 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
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
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376


                            


                                                                           
  



                                                                             
  

                                                                           







                                    
                                    
 




                                  








                                              

                           



                                                                           


                                        

                                                    









                                               



                                                     

  





                                                     























                                                                           











































                                                                   











































































































































                                                                                
                                   




                                    

                                                                        
 





                                                                         
                                                             








                                                              


                                     





                                                   





                                                                     
                                                                 

                                                                         
                                                                         
 




                                                                         

                                                                    
                                                                 
                                          
                                
 

                                                                           



                                      
                                                                




                                                                       
                                          

























                                                                        







                                                                    
                                 









                                                          
                                                                    
 
                                                 













                                                                          

























                                                                          
                                              



                                              
                                
 






                                                                                
 




                                                            

































                                                                        





































































































































































                                                                               
                                                                       



                                                                  









                                                                      


























































                                                                             
                                             



                                                   
                                 






























                                                                          






                                                        






































                                                                               
                                             



                                                   
                                 
























                                                                            






                                                        


























































                                                                             
                                     








                                                                   
                                   

         


















                                                                         
                                                  
                                           
                                
                                          



                                               

                                                                       




                                                                               

                                                             
                                       
                                 


                                             
                                                               
                                 
         

















                                                                               






































































































































































































































































































































































































                                                                               
/*
 * 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.
 *
 * 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 Lesser General Public License
 * along with this 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 <shell/e-shell.h>
#include <shell/e-shell-window.h>
#include <shell/e-shell-view.h>
#include <shell/e-shell-sidebar.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>

#include "em-folder-tree.h"

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

/* GtkAssistant's back button label. */
#define BACK_BUTTON_LABEL N_("Go _Back")

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_configured;

    /* GtkAssistant owns this. */
    GtkButton *back_button;  /* not referenced */
};

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);

    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. */

    /* For the summary page... */
    priv->auto_configured = TRUE;

    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_select_account_node (const gchar *account_uid)
{
    EShell *shell;
    EShellWindow *shell_window;
    EShellView *shell_view;
    EShellSidebar *shell_sidebar;
    EMFolderTree *folder_tree = NULL;
    GtkWindow *active_window;
    const gchar *active_view;

    g_return_if_fail (account_uid != NULL);

    shell = e_shell_get_default ();
    active_window = e_shell_get_active_window (shell);

    if (!E_IS_SHELL_WINDOW (active_window))
        return;

    shell_window = E_SHELL_WINDOW (active_window);
    active_view = e_shell_window_get_active_view (shell_window);

    if (g_strcmp0 (active_view, "mail") != 0)
        return;

    shell_view = e_shell_window_get_shell_view (shell_window, "mail");

    shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
    g_object_get (shell_sidebar, "folder-tree", &folder_tree, NULL);

    em_folder_tree_select_store_when_added (folder_tree, account_uid);

    g_object_unref (folder_tree);

}

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 {
        ESource *source;

        source = e_mail_config_assistant_get_account_source (assistant);
        if (source != NULL) {
            const gchar *uid;

            uid = e_source_get_uid (source);
            mail_config_assistant_select_account_node (uid);
        }

        gtk_widget_destroy (GTK_WIDGET (assistant));
    }
}

static void
mail_config_assistant_find_back_button_cb (GtkWidget *widget,
                                           gpointer user_data)
{
    EMailConfigAssistant *assistant;

    assistant = E_MAIL_CONFIG_ASSISTANT (user_data);

    if (GTK_IS_BUTTON (widget)) {
        GtkButton *button;
        const gchar *gtk_label;
        const gchar *our_label;

        button = GTK_BUTTON (widget);

        /* XXX The gtkassistant.ui file assigns the back button
         *     an ID of "back", but I don't think we have access
         *     to it from here.  I guess just compare by label,
         *     and hope our translation matches GTK's.  Yuck. */

        gtk_label = gtk_button_get_label (button);
        our_label = gettext (BACK_BUTTON_LABEL);

        if (g_strcmp0 (gtk_label, our_label) == 0)
            assistant->priv->back_button = button;

    } else if (GTK_IS_CONTAINER (widget)) {
        gtk_container_forall (
            GTK_CONTAINER (widget),
            mail_config_assistant_find_back_button_cb,
            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);
    gtk_window_set_default_size (GTK_WINDOW (assistant), 640, 480);

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

    /* XXX Locate the GtkAssistant's internal "Go Back" button so
     *     we can temporarily rename it for autoconfigure results.
     *     Walking the container like this is an extremely naughty
     *     and brittle hack, but GtkAssistant does not provide API
     *     to access it directly. */
    gtk_container_forall (
        GTK_CONTAINER (assistant),
        mail_config_assistant_find_back_button_cb,
        assistant);

    /* 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);

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

    e_signal_connect_notify (
        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);

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

    e_signal_connect_notify (
        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;
    }

    /* Are we viewing autoconfiguration results?  If so, temporarily
     * rename the back button to clarify that account details can be
     * revised.  Otherwise reset the button to its original label. */
    if (priv->back_button != NULL) {
        gboolean auto_configure_results;
        const gchar *label;

        auto_configure_results =
            E_IS_MAIL_CONFIG_SUMMARY_PAGE (page) &&
            priv->auto_configured && first_visit;

        if (auto_configure_results)
            label = _("_Revise Details");
        else
            label = gettext (BACK_BUTTON_LABEL);

        gtk_button_set_label (priv->back_button, label);
    }

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

        registry = e_mail_session_get_registry (priv->session);

        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 (
            registry,
            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);
}