aboutsummaryrefslogblamecommitdiffstats
path: root/e-util/e-source-selector.c
blob: e540248047767e991578a66f04a1b5bd17908cc1 (plain) (tree)
1
2
3
4
5
6
7
8
9
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
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




                                                                           


                                                                           
  



                                                                             
  

                                                                           




















                                                                





                                          









                                                     
                             
                            













                                  
                        














                                  
                         
                          
                          
















                                                                      



                                                                             




































































































































                                                                              




































                                                                         








































                                                                               
                                                        

























                                                                      
                                    
















                                                                         

                                                                           

                                                                        


                                                              












































                                                                               


                                                                     



                                                              




                                                                             

                               
                                                                             
                                          
         






























                                                                           


                                                                         




                                                             

                       









                                                             


                                                                         




                                                             

                       

                                                        
                                                        






                                                             


                                                                         




                                                             

                       







                                                             


                                                                         




                                                             

                       









                                                              


                                                                         




                                                             

                       









































































                                                                         





                                                         

                                                    
 
                                                           



                                                                      


                                                         

                                




























































































                                                                        





                                                             












































                                                                         






                                                                  

















                                                                       

                                                
                                       

                                                       

         





























                                                          
































                                                                           
                          



                                                             
                                       

                                                                        
                                                             
 
                                       

                                                                          
                                                               
 
                                       

                                                                          
                                                               
 
                                       

                                                                          
                                                               
 
                                       

                                                                           
                                                                











                                                            
                                 


































                                                                              
                         

                                          

















































































































































































































































































































































                                                                                     
                                                        

































































                                                                               










                                                 
















































































                                                                                  













                                                              
                                                              
                                                               
                                                              






                                                                         
                                                       





















                                                                  









                                                                  





















































































































                                                                         
                                                       









                                                             


















































                                                                            































                                                                      
                                                         



















                                                              
                             




                                                                             


                                                                           







                                   








                                                                             
  




                                              
    
       



                                                           
                             




                                                                     
                                      





                                                                         
                                                       


   


















































                                                                    




                                                          

















































































































































































































































































                                                                                






























                                                                     

































                                                                              
                                 




















































                                                                        























































                                                                            


                                       












                                                                          


                                                                             






                                                                            
                                                    
                                                      
                                                      









                                                           
                                               
                                                 
                                                 






                                                         






























                                                                         
/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* e-source-selector.c
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 * 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
 * published by the Free Software Foundation; either the
 *
 * 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/>.
 *
 * Author: Ettore Perazzoli <ettore@ximian.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include "e-cell-renderer-color.h"
#include "e-source-selector.h"

#define E_SOURCE_SELECTOR_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_SOURCE_SELECTOR, ESourceSelectorPrivate))

typedef struct _AsyncContext AsyncContext;

struct _ESourceSelectorPrivate {
    ESourceRegistry *registry;
    gulong source_added_handler_id;
    gulong source_changed_handler_id;
    gulong source_removed_handler_id;
    gulong source_enabled_handler_id;
    gulong source_disabled_handler_id;

    GHashTable *source_index;
    gchar *extension_name;

    GtkTreeRowReference *saved_primary_selection;

    /* ESource -> GSource */
    GHashTable *pending_writes;
    GMainContext *main_context;

    gboolean toggled_last;
    gboolean show_colors;
    gboolean show_icons;
    gboolean show_toggles;
};

struct _AsyncContext {
    ESourceSelector *selector;
    ESource *source;
};

enum {
    PROP_0,
    PROP_EXTENSION_NAME,
    PROP_PRIMARY_SELECTION,
    PROP_REGISTRY,
    PROP_SHOW_COLORS,
    PROP_SHOW_ICONS,
    PROP_SHOW_TOGGLES
};

enum {
    SELECTION_CHANGED,
    PRIMARY_SELECTION_CHANGED,
    POPUP_EVENT,
    DATA_DROPPED,
    NUM_SIGNALS
};

enum {
    COLUMN_NAME,
    COLUMN_COLOR,
    COLUMN_ACTIVE,
    COLUMN_ICON_NAME,
    COLUMN_SHOW_COLOR,
    COLUMN_SHOW_ICONS,
    COLUMN_SHOW_TOGGLE,
    COLUMN_WEIGHT,
    COLUMN_SOURCE,
    NUM_COLUMNS
};

static guint signals[NUM_SIGNALS];

G_DEFINE_TYPE (ESourceSelector, e_source_selector, GTK_TYPE_TREE_VIEW)

/* ESafeToggleRenderer does not emit 'toggled' signal
 * on 'activate' when mouse is not over the toggle. */

typedef GtkCellRendererToggle ECellRendererSafeToggle;
typedef GtkCellRendererToggleClass ECellRendererSafeToggleClass;

/* Forward Declarations */
GType       e_cell_renderer_safe_toggle_get_type
                        (void) G_GNUC_CONST;
static void selection_changed_callback  (GtkTreeSelection *selection,
                         ESourceSelector *selector);

G_DEFINE_TYPE (
    ECellRendererSafeToggle,
    e_cell_renderer_safe_toggle,
    GTK_TYPE_CELL_RENDERER_TOGGLE)

static gboolean
safe_toggle_activate (GtkCellRenderer *cell,
                      GdkEvent *event,
                      GtkWidget *widget,
                      const gchar *path,
                      const GdkRectangle *background_area,
                      const GdkRectangle *cell_area,
                      GtkCellRendererState flags)
{
    gboolean point_in_cell_area = TRUE;

    if (event->type == GDK_BUTTON_PRESS && cell_area != NULL) {
        cairo_region_t *region;

        region = cairo_region_create_rectangle (cell_area);
        point_in_cell_area = cairo_region_contains_point (
            region, event->button.x, event->button.y);
        cairo_region_destroy (region);
    }

    if (!point_in_cell_area)
        return FALSE;

    return GTK_CELL_RENDERER_CLASS (
        e_cell_renderer_safe_toggle_parent_class)->activate (
        cell, event, widget, path, background_area, cell_area, flags);
}

static void
e_cell_renderer_safe_toggle_class_init (ECellRendererSafeToggleClass *class)
{
    GtkCellRendererClass *cell_renderer_class;

    cell_renderer_class = GTK_CELL_RENDERER_CLASS (class);
    cell_renderer_class->activate = safe_toggle_activate;
}

static void
e_cell_renderer_safe_toggle_init (ECellRendererSafeToggle *obj)
{
}

static GtkCellRenderer *
e_cell_renderer_safe_toggle_new (void)
{
    return g_object_new (e_cell_renderer_safe_toggle_get_type (), NULL);
}

static void
clear_saved_primary_selection (ESourceSelector *selector)
{
    gtk_tree_row_reference_free (selector->priv->saved_primary_selection);
    selector->priv->saved_primary_selection = NULL;
}

static void
async_context_free (AsyncContext *async_context)
{
    if (async_context->selector != NULL)
        g_object_unref (async_context->selector);

    if (async_context->source != NULL)
        g_object_unref (async_context->source);

    g_slice_free (AsyncContext, async_context);
}

static void
pending_writes_destroy_source (GSource *source)
{
    g_source_destroy (source);
    g_source_unref (source);
}

static void
source_selector_write_done_cb (GObject *source_object,
                               GAsyncResult *result,
                               gpointer user_data)
{
    ESource *source;
    ESourceSelector *selector;
    GError *error = NULL;

    source = E_SOURCE (source_object);
    selector = E_SOURCE_SELECTOR (user_data);

    e_source_write_finish (source, result, &error);

    /* FIXME Display the error in the selector somehow? */
    if (error != NULL) {
        g_warning ("%s: %s", G_STRFUNC, error->message);
        g_error_free (error);
    }

    g_object_unref (selector);
}

static gboolean
source_selector_write_idle_cb (gpointer user_data)
{
    AsyncContext *async_context = user_data;
    GHashTable *pending_writes;

    /* XXX This operation is not cancellable. */
    e_source_write (
        async_context->source, NULL,
        source_selector_write_done_cb,
        g_object_ref (async_context->selector));

    pending_writes = async_context->selector->priv->pending_writes;
    g_hash_table_remove (pending_writes, async_context->source);

    return FALSE;
}

static void
source_selector_cancel_write (ESourceSelector *selector,
                              ESource *source)
{
    GHashTable *pending_writes;

    /* Cancel any pending writes for this ESource so as not
     * to overwrite whatever change we're being notified of. */
    pending_writes = selector->priv->pending_writes;
    g_hash_table_remove (pending_writes, source);
}

static const gchar *
source_selector_get_icon_name (ESourceSelector *selector,
                               ESource *source)
{
    const gchar *extension_name;
    const gchar *icon_name = NULL;

    /* XXX These are the same icons used in EShellView subclasses.
     *     We should really centralize these icon names somewhere. */

    extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
    if (e_source_has_extension (source, extension_name))
        icon_name = "x-office-address-book";

    extension_name = E_SOURCE_EXTENSION_CALENDAR;
    if (e_source_has_extension (source, extension_name))
        icon_name = "x-office-calendar";

    extension_name = E_SOURCE_EXTENSION_MAIL_ACCOUNT;
    if (e_source_has_extension (source, extension_name))
        icon_name = "evolution-mail";

    extension_name = E_SOURCE_EXTENSION_MAIL_TRANSPORT;
    if (e_source_has_extension (source, extension_name))
        icon_name = "mail-send";

    extension_name = E_SOURCE_EXTENSION_MEMO_LIST;
    if (e_source_has_extension (source, extension_name))
        icon_name = "evolution-memos";

    extension_name = E_SOURCE_EXTENSION_TASK_LIST;
    if (e_source_has_extension (source, extension_name))
        icon_name = "evolution-tasks";

    return icon_name;
}

static gboolean
source_selector_traverse (GNode *node,
                          ESourceSelector *selector)
{
    ESource *source;
    GHashTable *source_index;
    GtkTreeRowReference *reference = NULL;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;

    /* Skip the root node. */
    if (G_NODE_IS_ROOT (node))
        return FALSE;

    source_index = selector->priv->source_index;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));

    if (node->parent != NULL && node->parent->data != NULL)
        reference = g_hash_table_lookup (
            source_index, node->parent->data);

    if (gtk_tree_row_reference_valid (reference)) {
        GtkTreeIter parent;

        path = gtk_tree_row_reference_get_path (reference);
        gtk_tree_model_get_iter (model, &parent, path);
        gtk_tree_path_free (path);

        gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &parent);
    } else
        gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);

    source = E_SOURCE (node->data);

    path = gtk_tree_model_get_path (model, &iter);
    reference = gtk_tree_row_reference_new (model, path);
    g_hash_table_insert (source_index, g_object_ref (source), reference);
    gtk_tree_path_free (path);

    e_source_selector_update_row (selector, source);

    return FALSE;
}

static void
source_selector_save_expanded (GtkTreeView *tree_view,
                               GtkTreePath *path,
                               GQueue *queue)
{
    GtkTreeModel *model;
    GtkTreeIter iter;
    ESource *source;

    model = gtk_tree_view_get_model (tree_view);
    gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
    g_queue_push_tail (queue, source);
}

static void
source_selector_build_model (ESourceSelector *selector)
{
    ESourceRegistry *registry;
    GQueue queue = G_QUEUE_INIT;
    GHashTable *source_index;
    GtkTreeView *tree_view;
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    ESource *selected;
    const gchar *extension_name;
    GNode *root;

    tree_view = GTK_TREE_VIEW (selector);

    registry = e_source_selector_get_registry (selector);
    extension_name = e_source_selector_get_extension_name (selector);

    /* Make sure we have what we need to build the model, since
     * this can get called early in the initialization phase. */
    if (registry == NULL || extension_name == NULL)
        return;

    source_index = selector->priv->source_index;
    selected = e_source_selector_ref_primary_selection (selector);
    selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector));

    /* Signal is blocked to avoid "primary-selection-changed" signal
     * on model clear. */
    g_signal_handlers_block_matched (
        selection, G_SIGNAL_MATCH_FUNC,
        0, 0, NULL, selection_changed_callback, NULL);

    /* Save expanded sources to restore later. */
    gtk_tree_view_map_expanded_rows (
        tree_view, (GtkTreeViewMappingFunc)
        source_selector_save_expanded, &queue);

    model = gtk_tree_view_get_model (tree_view);
    gtk_tree_store_clear (GTK_TREE_STORE (model));

    g_hash_table_remove_all (source_index);

    root = e_source_registry_build_display_tree (registry, extension_name);

    g_node_traverse (
        root, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
        (GNodeTraverseFunc) source_selector_traverse,
        selector);

    e_source_registry_free_display_tree (root);

    /* Restore previously expanded sources. */
    while (!g_queue_is_empty (&queue)) {
        GtkTreeRowReference *reference;
        ESource *source;

        source = g_queue_pop_head (&queue);
        reference = g_hash_table_lookup (source_index, source);

        if (gtk_tree_row_reference_valid (reference)) {
            GtkTreePath *path;

            path = gtk_tree_row_reference_get_path (reference);
            gtk_tree_view_expand_to_path (tree_view, path);
            gtk_tree_path_free (path);
        }

        g_object_unref (source);
    }

    /* Restore the primary selection. */
    if (selected != NULL) {
        e_source_selector_set_primary_selection (selector, selected);
        g_object_unref (selected);
    }

    /* If the first succeeded, then there is no selection change,
     * thus no need for notification; notify about the change in
     * any other cases. */
    g_signal_handlers_unblock_matched (
        selection, G_SIGNAL_MATCH_FUNC,
        0, 0, NULL, selection_changed_callback, NULL);

    /* Make sure we have a primary selection.  If not, pick one. */
    selected = e_source_selector_ref_primary_selection (selector);
    if (selected == NULL) {
        selected = e_source_registry_ref_default_for_extension_name (
            registry, extension_name);
    }
    if (selected != NULL) {
        e_source_selector_set_primary_selection (selector, selected);
        g_object_unref (selected);
    }
}

static void
source_selector_expand_to_source (ESourceSelector *selector,
                                  ESource *source)
{
    GHashTable *source_index;
    GtkTreeRowReference *reference;
    GtkTreePath *path;

    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);

    /* If the ESource is not in our tree model then return silently. */
    if (reference == NULL)
        return;

    /* If we do have a row reference, it should be valid. */
    g_return_if_fail (gtk_tree_row_reference_valid (reference));

    /* Expand the tree view to the path containing the ESource */
    path = gtk_tree_row_reference_get_path (reference);
    gtk_tree_view_expand_to_path (GTK_TREE_VIEW (selector), path);
    gtk_tree_path_free (path);
}

static void
source_selector_source_added_cb (ESourceRegistry *registry,
                                 ESource *source,
                                 ESourceSelector *selector)
{
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (extension_name == NULL)
        return;

    if (!e_source_has_extension (source, extension_name))
        return;

    source_selector_build_model (selector);

    source_selector_expand_to_source (selector, source);
}

static void
source_selector_source_changed_cb (ESourceRegistry *registry,
                                   ESource *source,
                                   ESourceSelector *selector)
{
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (extension_name == NULL)
        return;

    if (!e_source_has_extension (source, extension_name))
        return;

    source_selector_cancel_write (selector, source);

    e_source_selector_update_row (selector, source);
}

static void
source_selector_source_removed_cb (ESourceRegistry *registry,
                                   ESource *source,
                                   ESourceSelector *selector)
{
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (extension_name == NULL)
        return;

    if (!e_source_has_extension (source, extension_name))
        return;

    source_selector_build_model (selector);
}

static void
source_selector_source_enabled_cb (ESourceRegistry *registry,
                                   ESource *source,
                                   ESourceSelector *selector)
{
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (extension_name == NULL)
        return;

    if (!e_source_has_extension (source, extension_name))
        return;

    source_selector_build_model (selector);

    source_selector_expand_to_source (selector, source);
}

static void
source_selector_source_disabled_cb (ESourceRegistry *registry,
                                    ESource *source,
                                    ESourceSelector *selector)
{
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (extension_name == NULL)
        return;

    if (!e_source_has_extension (source, extension_name))
        return;

    source_selector_build_model (selector);
}

static gboolean
same_source_name_exists (ESourceSelector *selector,
                         const gchar *display_name)
{
    GHashTable *source_index;
    GHashTableIter iter;
    gpointer key;

    source_index = selector->priv->source_index;
    g_hash_table_iter_init (&iter, source_index);

    while (g_hash_table_iter_next (&iter, &key, NULL)) {
        ESource *source = E_SOURCE (key);
        const gchar *source_name;

        source_name = e_source_get_display_name (source);
        if (g_strcmp0 (display_name, source_name) == 0)
            return TRUE;
    }

    return FALSE;
}

static gboolean
selection_func (GtkTreeSelection *selection,
                GtkTreeModel *model,
                GtkTreePath *path,
                gboolean path_currently_selected,
                ESourceSelector *selector)
{
    ESource *source;
    GtkTreeIter iter;
    const gchar *extension_name;

    if (selector->priv->toggled_last) {
        selector->priv->toggled_last = FALSE;
        return FALSE;
    }

    if (path_currently_selected)
        return TRUE;

    if (!gtk_tree_model_get_iter (model, &iter, path))
        return FALSE;

    extension_name = e_source_selector_get_extension_name (selector);
    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    if (!e_source_has_extension (source, extension_name)) {
        g_object_unref (source);
        return FALSE;
    }

    clear_saved_primary_selection (selector);

    g_object_unref (source);

    return TRUE;
}

static void
text_cell_edited_cb (ESourceSelector *selector,
                     const gchar *path_string,
                     const gchar *new_name)
{
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    ESource *source;

    if (new_name == NULL || *new_name == '\0')
        return;

    if (same_source_name_exists (selector, new_name))
        return;

    tree_view = GTK_TREE_VIEW (selector);
    model = gtk_tree_view_get_model (tree_view);

    path = gtk_tree_path_new_from_string (path_string);
    gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
    gtk_tree_path_free (path);

    e_source_set_display_name (source, new_name);

    e_source_selector_queue_write (selector, source);

    g_object_unref (source);
}

static void
cell_toggled_callback (GtkCellRendererToggle *renderer,
                       const gchar *path_string,
                       ESourceSelector *selector)
{
    ESource *source;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));
    path = gtk_tree_path_new_from_string (path_string);

    if (!gtk_tree_model_get_iter (model, &iter, path)) {
        gtk_tree_path_free (path);
        return;
    }

    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    if (e_source_selector_source_is_selected (selector, source))
        e_source_selector_unselect_source (selector, source);
    else
        e_source_selector_select_source (selector, source);

    selector->priv->toggled_last = TRUE;

    gtk_tree_path_free (path);

    g_object_unref (source);
}

static void
selection_changed_callback (GtkTreeSelection *selection,
                            ESourceSelector *selector)
{
    g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
    g_object_notify (G_OBJECT (selector), "primary-selection");
}

static void
source_selector_set_extension_name (ESourceSelector *selector,
                                    const gchar *extension_name)
{
    g_return_if_fail (extension_name != NULL);
    g_return_if_fail (selector->priv->extension_name == NULL);

    selector->priv->extension_name = g_strdup (extension_name);
}

static void
source_selector_set_registry (ESourceSelector *selector,
                              ESourceRegistry *registry)
{
    g_return_if_fail (E_IS_SOURCE_REGISTRY (registry));
    g_return_if_fail (selector->priv->registry == NULL);

    selector->priv->registry = g_object_ref (registry);
}

static void
source_selector_set_property (GObject *object,
                              guint property_id,
                              const GValue *value,
                              GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_EXTENSION_NAME:
            source_selector_set_extension_name (
                E_SOURCE_SELECTOR (object),
                g_value_get_string (value));
            return;

        case PROP_PRIMARY_SELECTION:
            e_source_selector_set_primary_selection (
                E_SOURCE_SELECTOR (object),
                g_value_get_object (value));
            return;

        case PROP_REGISTRY:
            source_selector_set_registry (
                E_SOURCE_SELECTOR (object),
                g_value_get_object (value));
            return;

        case PROP_SHOW_COLORS:
            e_source_selector_set_show_colors (
                E_SOURCE_SELECTOR (object),
                g_value_get_boolean (value));
            return;

        case PROP_SHOW_ICONS:
            e_source_selector_set_show_icons (
                E_SOURCE_SELECTOR (object),
                g_value_get_boolean (value));
            return;

        case PROP_SHOW_TOGGLES:
            e_source_selector_set_show_toggles (
                E_SOURCE_SELECTOR (object),
                g_value_get_boolean (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
source_selector_get_property (GObject *object,
                              guint property_id,
                              GValue *value,
                              GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_EXTENSION_NAME:
            g_value_set_string (
                value,
                e_source_selector_get_extension_name (
                E_SOURCE_SELECTOR (object)));
            return;

        case PROP_PRIMARY_SELECTION:
            g_value_take_object (
                value,
                e_source_selector_ref_primary_selection (
                E_SOURCE_SELECTOR (object)));
            return;

        case PROP_REGISTRY:
            g_value_set_object (
                value,
                e_source_selector_get_registry (
                E_SOURCE_SELECTOR (object)));
            return;

        case PROP_SHOW_COLORS:
            g_value_set_boolean (
                value,
                e_source_selector_get_show_colors (
                E_SOURCE_SELECTOR (object)));
            return;

        case PROP_SHOW_ICONS:
            g_value_set_boolean (
                value,
                e_source_selector_get_show_icons (
                E_SOURCE_SELECTOR (object)));
            return;

        case PROP_SHOW_TOGGLES:
            g_value_set_boolean (
                value,
                e_source_selector_get_show_toggles (
                E_SOURCE_SELECTOR (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
source_selector_dispose (GObject *object)
{
    ESourceSelectorPrivate *priv;

    priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);

    if (priv->source_added_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->registry,
            priv->source_added_handler_id);
        priv->source_added_handler_id = 0;
    }

    if (priv->source_changed_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->registry,
            priv->source_changed_handler_id);
        priv->source_changed_handler_id = 0;
    }

    if (priv->source_removed_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->registry,
            priv->source_removed_handler_id);
        priv->source_removed_handler_id = 0;
    }

    if (priv->source_enabled_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->registry,
            priv->source_enabled_handler_id);
        priv->source_enabled_handler_id = 0;
    }

    if (priv->source_disabled_handler_id > 0) {
        g_signal_handler_disconnect (
            priv->registry,
            priv->source_disabled_handler_id);
        priv->source_disabled_handler_id = 0;
    }

    g_clear_object (&priv->registry);

    g_hash_table_remove_all (priv->source_index);
    g_hash_table_remove_all (priv->pending_writes);

    clear_saved_primary_selection (E_SOURCE_SELECTOR (object));

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

static void
source_selector_finalize (GObject *object)
{
    ESourceSelectorPrivate *priv;

    priv = E_SOURCE_SELECTOR_GET_PRIVATE (object);

    g_hash_table_destroy (priv->source_index);
    g_hash_table_destroy (priv->pending_writes);

    g_free (priv->extension_name);

    if (priv->main_context != NULL)
        g_main_context_unref (priv->main_context);

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

static void
source_selector_constructed (GObject *object)
{
    ESourceRegistry *registry;
    ESourceSelector *selector;
    gulong handler_id;

    selector = E_SOURCE_SELECTOR (object);
    registry = e_source_selector_get_registry (selector);

    handler_id = g_signal_connect (
        registry, "source-added",
        G_CALLBACK (source_selector_source_added_cb), selector);
    selector->priv->source_added_handler_id = handler_id;

    handler_id = g_signal_connect (
        registry, "source-changed",
        G_CALLBACK (source_selector_source_changed_cb), selector);
    selector->priv->source_changed_handler_id = handler_id;

    handler_id = g_signal_connect (
        registry, "source-removed",
        G_CALLBACK (source_selector_source_removed_cb), selector);
    selector->priv->source_removed_handler_id = handler_id;

    handler_id = g_signal_connect (
        registry, "source-enabled",
        G_CALLBACK (source_selector_source_enabled_cb), selector);
    selector->priv->source_enabled_handler_id = handler_id;

    handler_id = g_signal_connect (
        registry, "source-disabled",
        G_CALLBACK (source_selector_source_disabled_cb), selector);
    selector->priv->source_disabled_handler_id = handler_id;

    source_selector_build_model (selector);

    gtk_tree_view_expand_all (GTK_TREE_VIEW (selector));
}

static gboolean
source_selector_button_press_event (GtkWidget *widget,
                                    GdkEventButton *event)
{
    ESourceSelector *selector;
    GtkWidgetClass *widget_class;
    GtkTreePath *path = NULL;
    ESource *source = NULL;
    ESource *primary;
    gboolean right_click = FALSE;
    gboolean triple_click = FALSE;
    gboolean row_exists;
    gboolean res = FALSE;

    selector = E_SOURCE_SELECTOR (widget);

    selector->priv->toggled_last = FALSE;

    /* Triple-clicking a source selects it exclusively. */

    if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
        right_click = TRUE;
    else if (event->button == 1 && event->type == GDK_3BUTTON_PRESS)
        triple_click = TRUE;
    else
        goto chainup;

    row_exists = gtk_tree_view_get_path_at_pos (
        GTK_TREE_VIEW (widget), event->x, event->y,
        &path, NULL, NULL, NULL);

    /* Get the source/group */
    if (row_exists) {
        GtkTreeModel *model;
        GtkTreeIter iter;

        model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));

        gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);
    }

    if (path != NULL)
        gtk_tree_path_free (path);

    if (source == NULL)
        goto chainup;

    primary = e_source_selector_ref_primary_selection (selector);
    if (source != primary)
        e_source_selector_set_primary_selection (selector, source);
    if (primary != NULL)
        g_object_unref (primary);

    if (right_click)
        g_signal_emit (
            widget, signals[POPUP_EVENT], 0, source, event, &res);

    if (triple_click) {
        e_source_selector_select_exclusive (selector, source);
        res = TRUE;
    }

    g_object_unref (source);

    return res;

chainup:

    /* Chain up to parent's button_press_event() method. */
    widget_class = GTK_WIDGET_CLASS (e_source_selector_parent_class);
    return widget_class->button_press_event (widget, event);
}

static void
source_selector_drag_leave (GtkWidget *widget,
                            GdkDragContext *context,
                            guint time_)
{
    GtkTreeView *tree_view;
    GtkTreeViewDropPosition pos;

    tree_view = GTK_TREE_VIEW (widget);
    pos = GTK_TREE_VIEW_DROP_BEFORE;

    gtk_tree_view_set_drag_dest_row (tree_view, NULL, pos);
}

static gboolean
source_selector_drag_motion (GtkWidget *widget,
                             GdkDragContext *context,
                             gint x,
                             gint y,
                             guint time_)
{
    ESource *source = NULL;
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreePath *path = NULL;
    GtkTreeIter iter;
    GtkTreeViewDropPosition pos;
    GdkDragAction action = 0;

    tree_view = GTK_TREE_VIEW (widget);
    model = gtk_tree_view_get_model (tree_view);

    if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
        goto exit;

    if (!gtk_tree_model_get_iter (model, &iter, path))
        goto exit;

    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    if (!e_source_get_writable (source))
        goto exit;

    pos = GTK_TREE_VIEW_DROP_INTO_OR_BEFORE;
    gtk_tree_view_set_drag_dest_row (tree_view, path, pos);

    if (gdk_drag_context_get_actions (context) & GDK_ACTION_MOVE)
        action = GDK_ACTION_MOVE;
    else
        action = gdk_drag_context_get_suggested_action (context);

exit:
    if (path != NULL)
        gtk_tree_path_free (path);

    if (source != NULL)
        g_object_unref (source);

    gdk_drag_status (context, action, time_);

    return TRUE;
}

static gboolean
source_selector_drag_drop (GtkWidget *widget,
                           GdkDragContext *context,
                           gint x,
                           gint y,
                           guint time_)
{
    ESource *source;
    ESourceSelector *selector;
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    const gchar *extension_name;
    gboolean drop_zone;
    gboolean valid;

    tree_view = GTK_TREE_VIEW (widget);
    model = gtk_tree_view_get_model (tree_view);

    if (!gtk_tree_view_get_path_at_pos (
        tree_view, x, y, &path, NULL, NULL, NULL))
        return FALSE;

    valid = gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_path_free (path);
    g_return_val_if_fail (valid, FALSE);

    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    selector = E_SOURCE_SELECTOR (widget);
    extension_name = e_source_selector_get_extension_name (selector);
    drop_zone = e_source_has_extension (source, extension_name);

    g_object_unref (source);

    return drop_zone;
}

static void
source_selector_drag_data_received (GtkWidget *widget,
                                    GdkDragContext *context,
                                    gint x,
                                    gint y,
                                    GtkSelectionData *selection_data,
                                    guint info,
                                    guint time_)
{
    ESource *source = NULL;
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreePath *path = NULL;
    GtkTreeIter iter;
    GdkDragAction action;
    gboolean delete;
    gboolean success = FALSE;

    tree_view = GTK_TREE_VIEW (widget);
    model = gtk_tree_view_get_model (tree_view);

    action = gdk_drag_context_get_selected_action (context);
    delete = (action == GDK_ACTION_MOVE);

    if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, NULL))
        goto exit;

    if (!gtk_tree_model_get_iter (model, &iter, path))
        goto exit;

    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    if (!e_source_get_writable (source))
        goto exit;

    g_signal_emit (
        widget, signals[DATA_DROPPED], 0, selection_data,
        source, gdk_drag_context_get_selected_action (context),
        info, &success);

exit:
    if (path != NULL)
        gtk_tree_path_free (path);

    if (source != NULL)
        g_object_unref (source);

    gtk_drag_finish (context, success, delete, time_);
}

static gboolean
source_selector_popup_menu (GtkWidget *widget)
{
    ESourceSelector *selector;
    ESource *source;
    gboolean res = FALSE;

    selector = E_SOURCE_SELECTOR (widget);
    source = e_source_selector_ref_primary_selection (selector);
    g_signal_emit (selector, signals[POPUP_EVENT], 0, source, NULL, &res);

    if (source != NULL)
        g_object_unref (source);

    return res;
}

static gboolean
source_selector_test_collapse_row (GtkTreeView *tree_view,
                                   GtkTreeIter *iter,
                                   GtkTreePath *path)
{
    ESourceSelectorPrivate *priv;
    GtkTreeSelection *selection;
    GtkTreeModel *model;
    GtkTreeIter child_iter;

    priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);

    /* Clear this because something else has been clicked on now */
    priv->toggled_last = FALSE;

    if (priv->saved_primary_selection)
        return FALSE;

    selection = gtk_tree_view_get_selection (tree_view);

    if (!gtk_tree_selection_get_selected (selection, &model, &child_iter))
        return FALSE;

    if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
        GtkTreeRowReference *reference;
        GtkTreePath *child_path;

        child_path = gtk_tree_model_get_path (model, &child_iter);
        reference = gtk_tree_row_reference_new (model, child_path);
        priv->saved_primary_selection = reference;
        gtk_tree_path_free (child_path);
    }

    return FALSE;
}

static void
source_selector_row_expanded (GtkTreeView *tree_view,
                              GtkTreeIter *iter,
                              GtkTreePath *path)
{
    ESourceSelectorPrivate *priv;
    GtkTreeModel *model;
    GtkTreePath *child_path;
    GtkTreeIter child_iter;

    priv = E_SOURCE_SELECTOR_GET_PRIVATE (tree_view);

    if (!priv->saved_primary_selection)
        return;

    model = gtk_tree_view_get_model (tree_view);

    child_path = gtk_tree_row_reference_get_path (
        priv->saved_primary_selection);
    gtk_tree_model_get_iter (model, &child_iter, child_path);

    if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &child_iter)) {
        GtkTreeSelection *selection;

        selection = gtk_tree_view_get_selection (tree_view);
        gtk_tree_selection_select_iter (selection, &child_iter);

        clear_saved_primary_selection (E_SOURCE_SELECTOR (tree_view));
    }

    gtk_tree_path_free (child_path);
}

static gboolean
source_selector_get_source_selected (ESourceSelector *selector,
                                     ESource *source)
{
    ESourceSelectable *extension;
    const gchar *extension_name;
    gboolean selected = TRUE;

    extension_name = e_source_selector_get_extension_name (selector);

    if (!e_source_has_extension (source, extension_name))
        return FALSE;

    extension = e_source_get_extension (source, extension_name);

    if (E_IS_SOURCE_SELECTABLE (extension))
        selected = e_source_selectable_get_selected (extension);

    return selected;
}

static void
source_selector_set_source_selected (ESourceSelector *selector,
                                     ESource *source,
                                     gboolean selected)
{
    ESourceSelectable *extension;
    const gchar *extension_name;

    extension_name = e_source_selector_get_extension_name (selector);

    if (!e_source_has_extension (source, extension_name))
        return;

    extension = e_source_get_extension (source, extension_name);

    if (!E_IS_SOURCE_SELECTABLE (extension))
        return;

    if (selected != e_source_selectable_get_selected (extension)) {
        e_source_selectable_set_selected (extension, selected);
        e_source_selector_queue_write (selector, source);
    }
}

static gboolean
ess_bool_accumulator (GSignalInvocationHint *ihint,
                      GValue *out,
                      const GValue *in,
                      gpointer data)
{
    gboolean v_boolean;

    v_boolean = g_value_get_boolean (in);
    g_value_set_boolean (out, v_boolean);

    return !v_boolean;
}

static void
e_source_selector_class_init (ESourceSelectorClass *class)
{
    GObjectClass *object_class;
    GtkWidgetClass *widget_class;
    GtkTreeViewClass *tree_view_class;

    g_type_class_add_private (class, sizeof (ESourceSelectorPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->set_property = source_selector_set_property;
    object_class->get_property = source_selector_get_property;
    object_class->dispose = source_selector_dispose;
    object_class->finalize = source_selector_finalize;
    object_class->constructed = source_selector_constructed;

    widget_class = GTK_WIDGET_CLASS (class);
    widget_class->button_press_event = source_selector_button_press_event;
    widget_class->drag_leave = source_selector_drag_leave;
    widget_class->drag_motion = source_selector_drag_motion;
    widget_class->drag_drop = source_selector_drag_drop;
    widget_class->drag_data_received = source_selector_drag_data_received;
    widget_class->popup_menu = source_selector_popup_menu;

    tree_view_class = GTK_TREE_VIEW_CLASS (class);
    tree_view_class->test_collapse_row = source_selector_test_collapse_row;
    tree_view_class->row_expanded = source_selector_row_expanded;

    class->get_source_selected = source_selector_get_source_selected;
    class->set_source_selected = source_selector_set_source_selected;

    g_object_class_install_property (
        object_class,
        PROP_EXTENSION_NAME,
        g_param_spec_string (
            "extension-name",
            NULL,
            NULL,
            NULL,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_PRIMARY_SELECTION,
        g_param_spec_object (
            "primary-selection",
            NULL,
            NULL,
            E_TYPE_SOURCE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_REGISTRY,
        g_param_spec_object (
            "registry",
            NULL,
            NULL,
            E_TYPE_SOURCE_REGISTRY,
            G_PARAM_READWRITE |
            G_PARAM_CONSTRUCT_ONLY |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_COLORS,
        g_param_spec_boolean (
            "show-colors",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_ICONS,
        g_param_spec_boolean (
            "show-icons",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    g_object_class_install_property (
        object_class,
        PROP_SHOW_TOGGLES,
        g_param_spec_boolean (
            "show-toggles",
            NULL,
            NULL,
            TRUE,
            G_PARAM_READWRITE |
            G_PARAM_STATIC_STRINGS));

    signals[SELECTION_CHANGED] = g_signal_new (
        "selection-changed",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ESourceSelectorClass, selection_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    /* XXX Consider this signal deprecated.  Connect
     *     to "notify::primary-selection" instead. */
    signals[PRIMARY_SELECTION_CHANGED] = g_signal_new (
        "primary-selection-changed",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ESourceSelectorClass, primary_selection_changed),
        NULL, NULL,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);

    signals[POPUP_EVENT] = g_signal_new (
        "popup-event",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ESourceSelectorClass, popup_event),
        ess_bool_accumulator, NULL, NULL,
        G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT,
        GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);

    signals[DATA_DROPPED] = g_signal_new (
        "data-dropped",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        G_STRUCT_OFFSET (ESourceSelectorClass, data_dropped),
        NULL, NULL, NULL,
        G_TYPE_BOOLEAN, 4,
        GTK_TYPE_SELECTION_DATA | G_SIGNAL_TYPE_STATIC_SCOPE,
        E_TYPE_SOURCE,
        GDK_TYPE_DRAG_ACTION,
        G_TYPE_UINT);
}

static void
e_source_selector_init (ESourceSelector *selector)
{
    GHashTable *pending_writes;
    GtkTreeViewColumn *column;
    GtkTreeSelection *selection;
    GtkCellRenderer *renderer;
    GtkTreeStore *tree_store;
    GtkTreeView *tree_view;

    pending_writes = g_hash_table_new_full (
        (GHashFunc) g_direct_hash,
        (GEqualFunc) g_direct_equal,
        (GDestroyNotify) g_object_unref,
        (GDestroyNotify) pending_writes_destroy_source);

    selector->priv = E_SOURCE_SELECTOR_GET_PRIVATE (selector);

    selector->priv->pending_writes = pending_writes;

    selector->priv->main_context = g_main_context_get_thread_default ();
    if (selector->priv->main_context != NULL)
        g_main_context_ref (selector->priv->main_context);

    tree_view = GTK_TREE_VIEW (selector);

    gtk_tree_view_set_search_column (tree_view, COLUMN_SOURCE);
    gtk_tree_view_set_enable_search (tree_view, TRUE);

    selector->priv->toggled_last = FALSE;
    selector->priv->show_colors = TRUE;
    selector->priv->show_toggles = TRUE;

    selector->priv->source_index = g_hash_table_new_full (
        (GHashFunc) e_source_hash,
        (GEqualFunc) e_source_equal,
        (GDestroyNotify) g_object_unref,
        (GDestroyNotify) gtk_tree_row_reference_free);

    tree_store = gtk_tree_store_new (
        NUM_COLUMNS,
        G_TYPE_STRING,      /* COLUMN_NAME */
        GDK_TYPE_COLOR,     /* COLUMN_COLOR */
        G_TYPE_BOOLEAN,     /* COLUMN_ACTIVE */
        G_TYPE_STRING,      /* COLUMN_ICON_NAME */
        G_TYPE_BOOLEAN,     /* COLUMN_SHOW_COLOR */
        G_TYPE_BOOLEAN,     /* COLUMN_SHOW_ICON */
        G_TYPE_BOOLEAN,     /* COLUMN_SHOW_TOGGLE */
        G_TYPE_INT,     /* COLUMN_WEIGHT */
        E_TYPE_SOURCE);     /* COLUMN_SOURCE */

    gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (tree_store));

    column = gtk_tree_view_column_new ();
    gtk_tree_view_column_set_expand (column, TRUE);
    gtk_tree_view_append_column (tree_view, column);

    renderer = e_cell_renderer_color_new ();
    g_object_set (
        G_OBJECT (renderer), "mode",
        GTK_CELL_RENDERER_MODE_ACTIVATABLE, NULL);
    gtk_tree_view_column_pack_start (column, renderer, FALSE);
    gtk_tree_view_column_add_attribute (
        column, renderer, "color", COLUMN_COLOR);
    gtk_tree_view_column_add_attribute (
        column, renderer, "visible", COLUMN_SHOW_COLOR);

    renderer = e_cell_renderer_safe_toggle_new ();
    gtk_tree_view_column_pack_start (column, renderer, FALSE);
    gtk_tree_view_column_add_attribute (
        column, renderer, "active", COLUMN_ACTIVE);
    gtk_tree_view_column_add_attribute (
        column, renderer, "visible", COLUMN_SHOW_TOGGLE);
    g_signal_connect (
        renderer, "toggled",
        G_CALLBACK (cell_toggled_callback), selector);

    renderer = gtk_cell_renderer_pixbuf_new ();
    g_object_set (
        G_OBJECT (renderer),
        "stock-size", GTK_ICON_SIZE_MENU, NULL);
    gtk_tree_view_column_pack_start (column, renderer, FALSE);
    gtk_tree_view_column_add_attribute (
        column, renderer, "icon-name", COLUMN_ICON_NAME);
    gtk_tree_view_column_add_attribute (
        column, renderer, "visible", COLUMN_SHOW_ICONS);

    renderer = gtk_cell_renderer_text_new ();
    g_object_set (
        G_OBJECT (renderer),
        "ellipsize", PANGO_ELLIPSIZE_END, NULL);
    g_signal_connect_swapped (
        renderer, "edited",
        G_CALLBACK (text_cell_edited_cb), selector);
    gtk_tree_view_column_pack_start (column, renderer, TRUE);
    gtk_tree_view_column_set_attributes (
        column, renderer,
        "text", COLUMN_NAME,
        "weight", COLUMN_WEIGHT,
        NULL);

    selection = gtk_tree_view_get_selection (tree_view);
    gtk_tree_selection_set_select_function (
        selection, (GtkTreeSelectionFunc)
        selection_func, selector, NULL);
    g_signal_connect_object (
        selection, "changed",
        G_CALLBACK (selection_changed_callback),
        G_OBJECT (selector), 0);

    gtk_tree_view_set_headers_visible (tree_view, FALSE);
}

/**
 * e_source_selector_new:
 * @registry: an #ESourceRegistry
 * @extension_name: the name of an #ESource extension
 *
 * Displays a list of sources from @registry having an extension named
 * @extension_name.  The sources are grouped by backend or groupware
 * account, which are described by the parent source.
 *
 * Returns: a new #ESourceSelector
 **/
GtkWidget *
e_source_selector_new (ESourceRegistry *registry,
                       const gchar *extension_name)
{
    g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
    g_return_val_if_fail (extension_name != NULL, NULL);

    return g_object_new (
        E_TYPE_SOURCE_SELECTOR, "registry", registry,
        "extension-name", extension_name, NULL);
}

/**
 * e_source_selector_get_registry:
 * @selector: an #ESourceSelector
 *
 * Returns the #ESourceRegistry that @selector is getting sources from.
 *
 * Returns: an #ESourceRegistry
 *
 * Since: 3.6
 **/
ESourceRegistry *
e_source_selector_get_registry (ESourceSelector *selector)
{
    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);

    return selector->priv->registry;
}

/**
 * e_source_selector_get_extension_name:
 * @selector: an #ESourceSelector
 *
 * Returns the extension name used to filter which sources are displayed.
 *
 * Returns: the #ESource extension name
 *
 * Since: 3.6
 **/
const gchar *
e_source_selector_get_extension_name (ESourceSelector *selector)
{
    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);

    return selector->priv->extension_name;
}

/**
 * e_source_selector_get_show_colors:
 * @selector: an #ESourceSelector
 *
 * Returns whether colors are shown next to data sources.
 *
 * Returns: %TRUE if colors are being shown
 *
 * Since: 3.6
 **/
gboolean
e_source_selector_get_show_colors (ESourceSelector *selector)
{
    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);

    return selector->priv->show_colors;
}

/**
 * e_source_selector_set_show_colors:
 * @selector: an #ESourceSelector
 * @show_colors: whether to show colors
 *
 * Sets whether to show colors next to data sources.
 *
 * Since: 3.6
 **/
void
e_source_selector_set_show_colors (ESourceSelector *selector,
                                   gboolean show_colors)
{
    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));

    if (show_colors == selector->priv->show_colors)
        return;

    selector->priv->show_colors = show_colors;

    g_object_notify (G_OBJECT (selector), "show-colors");

    source_selector_build_model (selector);
}

/**
 * e_source_selector_get_show_icons:
 * @selector: an #ESourceSelector
 *
 * Returns whether icons are shown next to data sources.
 *
 * Generally the icon shown will be based on the presence of a backend-based
 * extension, such as #ESourceAddressBook or #ESourceCalendar.  For #ESource
 * instances with no such extension, no icon is shown.
 *
 * Returns: %TRUE if icons are being shown
 *
 * Since: 3.12
 **/
gboolean
e_source_selector_get_show_icons (ESourceSelector *selector)
{
    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);

    return selector->priv->show_icons;
}

/**
 * e_source_selector_set_show_icons:
 * @selector: an #ESourceSelector
 * @show_icons: whether to show icons
 *
 * Sets whether to show icons next to data sources.
 *
 * Generally the icon shown will be based on the presence of a backend-based
 * extension, such as #ESourceAddressBook or #ESourceCalendar.  For #ESource
 * instances with no such extension, no icon is shown.
 *
 * Since: 3.12
 **/
void
e_source_selector_set_show_icons (ESourceSelector *selector,
                                  gboolean show_icons)
{
    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));

    if (show_icons == selector->priv->show_icons)
        return;

    selector->priv->show_icons = show_icons;

    g_object_notify (G_OBJECT (selector), "show-icons");

    source_selector_build_model (selector);
}

/**
 * e_source_selector_get_show_toggles:
 * @selector: an #ESourceSelector
 *
 * Returns whether toggles are shown next to data sources.
 *
 * Returns: %TRUE if toggles are being shown
 *
 * Since: 3.6
 **/
gboolean
e_source_selector_get_show_toggles (ESourceSelector *selector)
{
    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);

    return selector->priv->show_toggles;
}

/**
 * e_source_selector_set_show_toggles:
 * @selector: an #ESourceSelector
 * @show_toggles: whether to show toggles
 *
 * Sets whether to show toggles next to data sources.
 *
 * Since: 3.6
 **/
void
e_source_selector_set_show_toggles (ESourceSelector *selector,
                                   gboolean show_toggles)
{
    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));

    if (show_toggles == selector->priv->show_toggles)
        return;

    selector->priv->show_toggles = show_toggles;

    g_object_notify (G_OBJECT (selector), "show-toggles");

    source_selector_build_model (selector);
}

/* Helper for e_source_selector_get_selection() */
static gboolean
source_selector_check_selected (GtkTreeModel *model,
                                GtkTreePath *path,
                                GtkTreeIter *iter,
                                gpointer user_data)
{
    ESource *source;

    struct {
        ESourceSelector *selector;
        GQueue queue;
    } *closure = user_data;

    gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);

    if (e_source_selector_source_is_selected (closure->selector, source))
        g_queue_push_tail (&closure->queue, g_object_ref (source));

    g_object_unref (source);

    return FALSE;
}

/**
 * e_source_selector_get_selection:
 * @selector: an #ESourceSelector
 *
 * Returns a list of selected sources, i.e. those that were enabled through
 * the corresponding checkboxes in the tree.  The sources are ordered as they
 * appear in @selector.
 *
 * The sources returned in the list are referenced for thread-safety.
 * They must each be unreferenced with g_object_unref() when finished
 * with them.  Free the returned list itself with g_list_free().
 *
 * An easy way to free the list properly in one step is as follows:
 *
 * |[
 *   g_list_free_full (list, g_object_unref);
 * ]|
 *
 * Returns: a ordered list of selected sources
 **/
GList *
e_source_selector_get_selection (ESourceSelector *selector)
{
    struct {
        ESourceSelector *selector;
        GQueue queue;
    } closure;

    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);

    closure.selector = selector;
    g_queue_init (&closure.queue);

    gtk_tree_model_foreach (
        gtk_tree_view_get_model (GTK_TREE_VIEW (selector)),
        (GtkTreeModelForeachFunc) source_selector_check_selected,
        &closure);

    return g_queue_peek_head_link (&closure.queue);
}

/**
 * e_source_selector_select_source:
 * @selector: An #ESourceSelector widget
 * @source: An #ESource.
 *
 * Select @source in @selector.
 **/
void
e_source_selector_select_source (ESourceSelector *selector,
                                 ESource *source)
{
    ESourceSelectorClass *class;
    GtkTreeRowReference *reference;
    GHashTable *source_index;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    /* Make sure the ESource is in our tree model. */
    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);
    g_return_if_fail (gtk_tree_row_reference_valid (reference));

    class = E_SOURCE_SELECTOR_GET_CLASS (selector);
    g_return_if_fail (class->set_source_selected != NULL);

    class->set_source_selected (selector, source, TRUE);

    g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
}

/**
 * e_source_selector_unselect_source:
 * @selector: An #ESourceSelector widget
 * @source: An #ESource.
 *
 * Unselect @source in @selector.
 **/
void
e_source_selector_unselect_source (ESourceSelector *selector,
                                   ESource *source)
{
    ESourceSelectorClass *class;
    GtkTreeRowReference *reference;
    GHashTable *source_index;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    /* Make sure the ESource is in our tree model. */
    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);

    /* can be NULL when the source was just removed */
    if (!reference)
        return;

    g_return_if_fail (gtk_tree_row_reference_valid (reference));

    class = E_SOURCE_SELECTOR_GET_CLASS (selector);
    g_return_if_fail (class->set_source_selected != NULL);

    class->set_source_selected (selector, source, FALSE);

    g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
}

/**
 * e_source_selector_select_exclusive:
 * @selector: An #ESourceSelector widget
 * @source: An #ESource.
 *
 * Select @source in @selector and unselect all others.
 *
 * Since: 2.30
 **/
void
e_source_selector_select_exclusive (ESourceSelector *selector,
                                    ESource *source)
{
    ESourceSelectorClass *class;
    GHashTable *source_index;
    GHashTableIter iter;
    gpointer key;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    class = E_SOURCE_SELECTOR_GET_CLASS (selector);
    g_return_if_fail (class->set_source_selected != NULL);

    source_index = selector->priv->source_index;
    g_hash_table_iter_init (&iter, source_index);

    while (g_hash_table_iter_next (&iter, &key, NULL)) {
        gboolean selected = e_source_equal (key, source);
        class->set_source_selected (selector, key, selected);
    }

    g_signal_emit (selector, signals[SELECTION_CHANGED], 0);
}

/**
 * e_source_selector_source_is_selected:
 * @selector: An #ESourceSelector widget
 * @source: An #ESource.
 *
 * Check whether @source is selected in @selector.
 *
 * Returns: %TRUE if @source is currently selected, %FALSE otherwise.
 **/
gboolean
e_source_selector_source_is_selected (ESourceSelector *selector,
                                      ESource *source)
{
    ESourceSelectorClass *class;
    GtkTreeRowReference *reference;
    GHashTable *source_index;

    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), FALSE);
    g_return_val_if_fail (E_IS_SOURCE (source), FALSE);

    /* Make sure the ESource is in our tree model. */
    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);
    g_return_val_if_fail (gtk_tree_row_reference_valid (reference), FALSE);

    class = E_SOURCE_SELECTOR_GET_CLASS (selector);
    g_return_val_if_fail (class->get_source_selected != NULL, FALSE);

    return class->get_source_selected (selector, source);
}

/**
 * e_source_selector_edit_primary_selection:
 * @selector: An #ESourceSelector widget
 *
 * Allows the user to rename the primary selected source by opening an
 * entry box directly in @selector.
 *
 * Since: 2.26
 **/
void
e_source_selector_edit_primary_selection (ESourceSelector *selector)
{
    GtkTreeRowReference *reference;
    GtkTreeSelection *selection;
    GtkTreeViewColumn *column;
    GtkCellRenderer *renderer;
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreePath *path = NULL;
    GtkTreeIter iter;
    GList *list;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));

    tree_view = GTK_TREE_VIEW (selector);
    column = gtk_tree_view_get_column (tree_view, 0);
    reference = selector->priv->saved_primary_selection;
    selection = gtk_tree_view_get_selection (tree_view);

    if (reference != NULL)
        path = gtk_tree_row_reference_get_path (reference);
    else if (gtk_tree_selection_get_selected (selection, &model, &iter))
        path = gtk_tree_model_get_path (model, &iter);

    if (path == NULL)
        return;

    /* XXX Because we stuff three renderers in a single column,
     *     we have to manually hunt for the text renderer. */
    renderer = NULL;
    list = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
    while (list != NULL) {
        renderer = list->data;
        if (GTK_IS_CELL_RENDERER_TEXT (renderer))
            break;
        list = g_list_delete_link (list, list);
    }
    g_list_free (list);

    /* Make the text cell renderer editable, but only temporarily.
     * We don't want editing to be activated by simply clicking on
     * the source name.  Too easy for accidental edits to occur. */
    g_object_set (renderer, "editable", TRUE, NULL);
    gtk_tree_view_expand_to_path (tree_view, path);
    gtk_tree_view_set_cursor_on_cell (
        tree_view, path, column, renderer, TRUE);
    g_object_set (renderer, "editable", FALSE, NULL);

    gtk_tree_path_free (path);
}

/**
 * e_source_selector_ref_primary_selection:
 * @selector: An #ESourceSelector widget
 *
 * Get the primary selected source.  The primary selection is the one that is
 * highlighted through the normal #GtkTreeView selection mechanism (as opposed
 * to the "normal" selection, which is the set of source whose checkboxes are
 * checked).
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: The selected source.
 *
 * Since: 3.6
 **/
ESource *
e_source_selector_ref_primary_selection (ESourceSelector *selector)
{
    ESource *source;
    GtkTreeRowReference *reference;
    GtkTreeSelection *selection;
    GtkTreeView *tree_view;
    GtkTreeModel *model;
    GtkTreeIter iter;
    const gchar *extension_name;
    gboolean have_iter = FALSE;

    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);

    tree_view = GTK_TREE_VIEW (selector);
    model = gtk_tree_view_get_model (tree_view);
    selection = gtk_tree_view_get_selection (tree_view);

    reference = selector->priv->saved_primary_selection;

    if (gtk_tree_row_reference_valid (reference)) {
        GtkTreePath *path;

        path = gtk_tree_row_reference_get_path (reference);
        have_iter = gtk_tree_model_get_iter (model, &iter, path);
        gtk_tree_path_free (path);
    }

    if (!have_iter)
        have_iter = gtk_tree_selection_get_selected (
            selection, NULL, &iter);

    if (!have_iter)
        return NULL;

    gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    extension_name = e_source_selector_get_extension_name (selector);

    if (!e_source_has_extension (source, extension_name)) {
        g_object_unref (source);
        return NULL;
    }

    return source;
}

/**
 * e_source_selector_set_primary_selection:
 * @selector: an #ESourceSelector widget
 * @source: an #ESource to select
 *
 * Highlights @source in @selector.  The highlighted #ESource is called
 * the primary selection.
 *
 * Do not confuse this function with e_source_selector_select_source(),
 * which activates the check box next to an #ESource's display name in
 * @selector.  This function does not alter the check box.
 **/
void
e_source_selector_set_primary_selection (ESourceSelector *selector,
                                         ESource *source)
{
    GHashTable *source_index;
    GtkTreeRowReference *reference;
    GtkTreeSelection *selection;
    GtkTreeView *tree_view;
    GtkTreePath *child_path;
    GtkTreePath *parent_path;
    const gchar *extension_name;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    tree_view = GTK_TREE_VIEW (selector);
    selection = gtk_tree_view_get_selection (tree_view);

    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);

    /* XXX Maybe we should return a success/fail boolean? */
    if (!gtk_tree_row_reference_valid (reference))
        return;

    extension_name = e_source_selector_get_extension_name (selector);

    /* Return silently if attempting to select a parent node
     * lacking the expected extension (e.g. On This Computer). */
    if (!e_source_has_extension (source, extension_name))
        return;

    /* We block the signal because this all needs to be atomic */
    g_signal_handlers_block_matched (
        selection, G_SIGNAL_MATCH_FUNC,
        0, 0, NULL, selection_changed_callback, NULL);
    gtk_tree_selection_unselect_all (selection);
    g_signal_handlers_unblock_matched (
        selection, G_SIGNAL_MATCH_FUNC,
        0, 0, NULL, selection_changed_callback, NULL);

    clear_saved_primary_selection (selector);

    child_path = gtk_tree_row_reference_get_path (reference);

    parent_path = gtk_tree_path_copy (child_path);
    gtk_tree_path_up (parent_path);

    if (gtk_tree_view_row_expanded (tree_view, parent_path)) {
        gtk_tree_selection_select_path (selection, child_path);
    } else {
        selector->priv->saved_primary_selection =
            gtk_tree_row_reference_copy (reference);
        g_signal_emit (selector, signals[PRIMARY_SELECTION_CHANGED], 0);
        g_object_notify (G_OBJECT (selector), "primary-selection");
    }

    gtk_tree_path_free (child_path);
    gtk_tree_path_free (parent_path);
}

/**
 * e_source_selector_ref_source_by_iter:
 * @selector: an #ESourceSelector
 * @iter: a #GtkTreeIter
 *
 * Returns the #ESource object at @iter.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: the #ESource object at @iter, or %NULL
 *
 * Since: 3.8
 **/
ESource *
e_source_selector_ref_source_by_iter (ESourceSelector *selector,
                                      GtkTreeIter *iter)
{
    ESource *source = NULL;
    GtkTreeModel *model;

    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
    g_return_val_if_fail (iter != NULL, NULL);

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));

    gtk_tree_model_get (model, iter, COLUMN_SOURCE, &source, -1);

    return source;
}

/**
 * e_source_selector_ref_source_by_path:
 * @selector: an #ESourceSelector
 * @path: a #GtkTreePath
 *
 * Returns the #ESource object at @path, or %NULL if @path is invalid.
 *
 * The returned #ESource is referenced for thread-safety and must be
 * unreferenced with g_object_unref() when finished with it.
 *
 * Returns: the #ESource object at @path, or %NULL
 *
 * Since: 3.6
 **/
ESource *
e_source_selector_ref_source_by_path (ESourceSelector *selector,
                                      GtkTreePath *path)
{
    ESource *source = NULL;
    GtkTreeModel *model;
    GtkTreeIter iter;

    g_return_val_if_fail (E_IS_SOURCE_SELECTOR (selector), NULL);
    g_return_val_if_fail (path != NULL, NULL);

    model = gtk_tree_view_get_model (GTK_TREE_VIEW (selector));

    if (gtk_tree_model_get_iter (model, &iter, path))
        gtk_tree_model_get (model, &iter, COLUMN_SOURCE, &source, -1);

    return source;
}

/**
 * e_source_selector_queue_write:
 * @selector: an #ESourceSelector
 * @source: an #ESource with changes to be written
 *
 * Queues a main loop idle callback to write changes to @source back to
 * the D-Bus registry service.
 *
 * Since: 3.6
 **/
void
e_source_selector_queue_write (ESourceSelector *selector,
                               ESource *source)
{
    GSource *idle_source;
    GHashTable *pending_writes;
    GMainContext *main_context;
    AsyncContext *async_context;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    main_context = selector->priv->main_context;
    pending_writes = selector->priv->pending_writes;

    idle_source = g_hash_table_lookup (pending_writes, source);
    if (idle_source != NULL && !g_source_is_destroyed (idle_source))
        return;

    async_context = g_slice_new0 (AsyncContext);
    async_context->selector = g_object_ref (selector);
    async_context->source = g_object_ref (source);

    /* Set a higher priority so this idle source runs before our
     * source_selector_cancel_write() signal handler, which will
     * cancel this idle source.  Cancellation is the right thing
     * to do when receiving changes from OTHER registry clients,
     * but we don't want to cancel our own changes.
     *
     * XXX This might be an argument for using etags.
     */
    idle_source = g_idle_source_new ();
    g_hash_table_insert (
        pending_writes,
        g_object_ref (source),
        g_source_ref (idle_source));
    g_source_set_callback (
        idle_source,
        source_selector_write_idle_cb,
        async_context,
        (GDestroyNotify) async_context_free);
    g_source_set_priority (idle_source, G_PRIORITY_HIGH_IDLE);
    g_source_attach (idle_source, main_context);
    g_source_unref (idle_source);
}

/**
 * e_source_selector_update_row:
 * @selector: an #ESourceSelector
 * @source: an #ESource
 *
 * Updates the corresponding #GtkTreeModel row for @source.
 *
 * This function is public so it can be called from subclasses like
 * #EClientSelector.
 *
 * Since: 3.8
 **/
void
e_source_selector_update_row (ESourceSelector *selector,
                              ESource *source)
{
    GHashTable *source_index;
    ESourceExtension *extension = NULL;
    GtkTreeRowReference *reference;
    GtkTreeModel *model;
    GtkTreePath *path;
    GtkTreeIter iter;
    const gchar *extension_name;
    const gchar *display_name;
    gboolean selected;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));
    g_return_if_fail (E_IS_SOURCE (source));

    source_index = selector->priv->source_index;
    reference = g_hash_table_lookup (source_index, source);

    /* This function runs when ANY ESource in the registry changes.
     * If the ESource is not in our tree model then return silently. */
    if (reference == NULL)
        return;

    /* If we do have a row reference, it should be valid. */
    g_return_if_fail (gtk_tree_row_reference_valid (reference));

    model = gtk_tree_row_reference_get_model (reference);
    path = gtk_tree_row_reference_get_path (reference);
    gtk_tree_model_get_iter (model, &iter, path);
    gtk_tree_path_free (path);

    display_name = e_source_get_display_name (source);

    extension_name = e_source_selector_get_extension_name (selector);
    selected = e_source_selector_source_is_selected (selector, source);

    if (e_source_has_extension (source, extension_name))
        extension = e_source_get_extension (source, extension_name);

    if (extension != NULL) {
        GdkColor color;
        const gchar *color_spec = NULL;
        const gchar *icon_name;
        gboolean show_color;
        gboolean show_icons;
        gboolean show_toggle;

        show_color =
            E_IS_SOURCE_SELECTABLE (extension) &&
            e_source_selector_get_show_colors (selector);

        if (show_color)
            color_spec = e_source_selectable_get_color (
                E_SOURCE_SELECTABLE (extension));

        if (color_spec != NULL && *color_spec != '\0')
            show_color = gdk_color_parse (color_spec, &color);

        show_icons = e_source_selector_get_show_icons (selector);
        icon_name = source_selector_get_icon_name (selector, source);

        show_toggle = e_source_selector_get_show_toggles (selector);

        gtk_tree_store_set (
            GTK_TREE_STORE (model), &iter,
            COLUMN_NAME, display_name,
            COLUMN_COLOR, show_color ? &color : NULL,
            COLUMN_ACTIVE, selected,
            COLUMN_ICON_NAME, icon_name,
            COLUMN_SHOW_COLOR, show_color,
            COLUMN_SHOW_ICONS, show_icons,
            COLUMN_SHOW_TOGGLE, show_toggle,
            COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
            COLUMN_SOURCE, source,
            -1);
    } else {
        gtk_tree_store_set (
            GTK_TREE_STORE (model), &iter,
            COLUMN_NAME, display_name,
            COLUMN_COLOR, NULL,
            COLUMN_ACTIVE, FALSE,
            COLUMN_ICON_NAME, NULL,
            COLUMN_SHOW_COLOR, FALSE,
            COLUMN_SHOW_ICONS, FALSE,
            COLUMN_SHOW_TOGGLE, FALSE,
            COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
            COLUMN_SOURCE, source,
            -1);
    }
}

/**
 * e_source_selector_update_all_rows:
 * @selector: an #ESourceSelector
 *
 * Calls e_source_selector_update_row() for each #ESource being shown by
 * @selector, according to the #ESourceSelector:extension_name property.
 *
 * Since: 3.10
 **/
void
e_source_selector_update_all_rows (ESourceSelector *selector)
{
    ESourceRegistry *registry;
    GList *list, *link;
    const gchar *extension_name;

    g_return_if_fail (E_IS_SOURCE_SELECTOR (selector));

    registry = e_source_selector_get_registry (selector);
    extension_name = e_source_selector_get_extension_name (selector);

    list = e_source_registry_list_sources (registry, extension_name);

    for (link = list; link != NULL; link = g_list_next (link)) {
        ESource *source = E_SOURCE (link->data);
        e_source_selector_update_row (selector, source);
    }

    g_list_free_full (list, (GDestroyNotify) g_object_unref);
}