/* * Evolution calendar importer component * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Chris Toshok * JP Rosevear * Michael Zucchi * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "e-util/e-import.h" #include "e-util/e-datetime-format.h" #include "misc/e-web-view-preview.h" #include "evolution-addressbook-importers.h" enum _VCardEncoding { VCARD_ENCODING_NONE, VCARD_ENCODING_UTF8, VCARD_ENCODING_UTF16, VCARD_ENCODING_LOCALE }; typedef enum _VCardEncoding VCardEncoding; typedef struct { EImport *import; EImportTarget *target; guint idle_id; gint state; /* 0 - importing, 1 - cancelled/complete */ gint total; gint count; ESource *primary; GSList *contactlist; GSList *iterator; EBookClient *book_client; /* when opening book */ gchar *contents; VCardEncoding encoding; } VCardImporter; static void vcard_import_done (VCardImporter *gci); static void add_to_notes (EContact *contact, EContactField field) { const gchar *old_text; const gchar *field_text; gchar *new_text; old_text = e_contact_get_const (contact, E_CONTACT_NOTE); if (old_text && strstr (old_text, e_contact_pretty_name (field))) return; field_text = e_contact_get_const (contact, field); if (!field_text || !*field_text) return; new_text = g_strdup_printf ("%s%s%s: %s", old_text ? old_text : "", old_text && *old_text && *(old_text + strlen (old_text) - 1) != '\n' ? "\n" : "", e_contact_pretty_name (field), field_text); e_contact_set (contact, E_CONTACT_NOTE, new_text); g_free (new_text); } static void vcard_import_contact (VCardImporter *gci, EContact *contact) { EContactPhoto *photo; GList *attrs, *attr; gchar *uid = NULL; /* Apple's addressbook.app exports PHOTO's without a TYPE param, so let's figure out the format here if there's a PHOTO attribute missing a TYPE param. this is sort of a hack, as EContact sets the type for us if we use the setter. so let's e_contact_get + e_contact_set on E_CONTACT_PHOTO. */ photo = e_contact_get (contact, E_CONTACT_PHOTO); if (photo) { e_contact_set (contact, E_CONTACT_PHOTO, photo); e_contact_photo_free (photo); } /* Deal with our XML EDestination stuff in EMAIL attributes, if there is any. */ attrs = e_contact_get_attributes (contact, E_CONTACT_EMAIL); for (attr = attrs; attr; attr = attr->next) { EVCardAttribute *a = attr->data; GList *v = e_vcard_attribute_get_values (a); if (v && v->data) { if (!strncmp ((gchar *)v->data, "data); e_destination_export_to_vcard_attribute (dest, a); g_object_unref (dest); } } } e_contact_set_attributes (contact, E_CONTACT_EMAIL, attrs); /* Deal with TEL attributes that don't conform to what we need. 1. if there's no location (HOME/WORK/OTHER), default to OTHER. 2. if there's *only* a location specified, default to VOICE. */ attrs = e_vcard_get_attributes (E_VCARD (contact)); for (attr = attrs; attr; attr = attr->next) { EVCardAttribute *a = attr->data; gboolean location_only = TRUE; gboolean no_location = TRUE; gboolean is_work_home = FALSE; GList *params, *param; if (g_ascii_strcasecmp (e_vcard_attribute_get_name (a), EVC_TEL)) continue; params = e_vcard_attribute_get_params (a); for (param = params; param; param = param->next) { EVCardAttributeParam *p = param->data; GList *vs, *v; if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (p), EVC_TYPE)) continue; vs = e_vcard_attribute_param_get_values (p); for (v = vs; v; v = v->next) { is_work_home = is_work_home || !g_ascii_strcasecmp ((gchar *)v->data, "WORK") || !g_ascii_strcasecmp ((gchar *)v->data, "HOME"); if (!g_ascii_strcasecmp ((gchar *)v->data, "WORK") || !g_ascii_strcasecmp ((gchar *)v->data, "HOME") || !g_ascii_strcasecmp ((gchar *)v->data, "OTHER")) no_location = FALSE; else location_only = FALSE; } } if (is_work_home) { /* only WORK and HOME phone numbers require locations, the rest should be kept as is */ if (location_only) { /* add VOICE */ e_vcard_attribute_add_param_with_value (a, e_vcard_attribute_param_new (EVC_TYPE), "VOICE"); } if (no_location) { /* add OTHER */ e_vcard_attribute_add_param_with_value (a, e_vcard_attribute_param_new (EVC_TYPE), "OTHER"); } } } /* Deal with ADR and EMAIL attributes that don't conform to what we need. if HOME or WORK isn't specified, add TYPE=OTHER. */ attrs = e_vcard_get_attributes (E_VCARD (contact)); for (attr = attrs; attr; attr = attr->next) { EVCardAttribute *a = attr->data; gboolean no_location = TRUE; GList *params, *param; if (g_ascii_strcasecmp (e_vcard_attribute_get_name (a), EVC_ADR) && g_ascii_strcasecmp (e_vcard_attribute_get_name (a), EVC_EMAIL)) continue; params = e_vcard_attribute_get_params (a); for (param = params; param; param = param->next) { EVCardAttributeParam *p = param->data; GList *vs, *v; if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (p), EVC_TYPE)) continue; vs = e_vcard_attribute_param_get_values (p); for (v = vs; v; v = v->next) { if (!g_ascii_strcasecmp ((gchar *)v->data, "WORK") || !g_ascii_strcasecmp ((gchar *)v->data, "HOME")) no_location = FALSE; } } if (no_location) { /* add OTHER */ e_vcard_attribute_add_param_with_value (a, e_vcard_attribute_param_new (EVC_TYPE), "OTHER"); } } /* Work around the fact that these fields no longer show up in the UI */ add_to_notes (contact, E_CONTACT_OFFICE); add_to_notes (contact, E_CONTACT_SPOUSE); add_to_notes (contact, E_CONTACT_BLOG_URL); /* FIXME Error checking */ if (e_book_client_add_contact_sync (gci->book_client, contact, &uid, NULL, NULL) && uid) { e_contact_set (contact, E_CONTACT_UID, uid); g_free (uid); } } static gboolean vcard_import_contacts (gpointer data) { VCardImporter *gci = data; gint count = 0; GSList *iterator = gci->iterator; if (gci->state == 0) { while (count < 50 && iterator) { vcard_import_contact (gci, iterator->data); count++; iterator = iterator->next; } gci->count += count; gci->iterator = iterator; if (iterator == NULL) gci->state = 1; } if (gci->state == 1) { vcard_import_done (gci); return FALSE; } else { e_import_status ( gci->import, gci->target, _("Importing..."), gci->count * 100 / gci->total); return TRUE; } } #define BOM (gunichar2)0xFEFF #define ANTIBOM (gunichar2)0xFFFE static gboolean has_bom (const gunichar2 *utf16) { if ((utf16 == NULL) || (*utf16 == '\0')) { return FALSE; } return ((*utf16 == BOM) || (*utf16 == ANTIBOM)); } static void fix_utf16_endianness (gunichar2 *utf16) { gunichar2 *it; if ((utf16 == NULL) || (*utf16 == '\0')) { return; } if (*utf16 != ANTIBOM) { return; } for (it = utf16; *it != '\0'; it++) { *it = GUINT16_SWAP_LE_BE (*it); } } /* Converts an UTF-16 string to an UTF-8 string removing the BOM character * WARNING: this may modify the utf16 argument if the function detects the * string isn't using the local endianness */ static gchar * utf16_to_utf8 (gunichar2 *utf16) { if (utf16 == NULL) { return NULL; } fix_utf16_endianness (utf16); if (*utf16 == BOM) { utf16++; } return g_utf16_to_utf8 (utf16, -1, NULL, NULL, NULL); } /* Actually check the contents of this file */ static VCardEncoding guess_vcard_encoding (const gchar *filename) { FILE *handle; gchar line[4096]; gchar *line_utf8; VCardEncoding encoding = VCARD_ENCODING_NONE; handle = g_fopen (filename, "r"); if (handle == NULL) { g_print ("\n"); return VCARD_ENCODING_NONE; } fgets (line, 4096, handle); if (line == NULL) { fclose (handle); g_print ("\n"); return VCARD_ENCODING_NONE; } fclose (handle); if (has_bom ((gunichar2*) line)) { gunichar2 *utf16 = (gunichar2*) line; /* Check for a BOM to try to detect UTF-16 encoded vcards * (MacOSX address book creates such vcards for example) */ line_utf8 = utf16_to_utf8 (utf16); if (line_utf8 == NULL) { return VCARD_ENCODING_NONE; } encoding = VCARD_ENCODING_UTF16; } else if (g_utf8_validate (line, -1, NULL)) { line_utf8 = g_strdup (line); encoding = VCARD_ENCODING_UTF8; } else { line_utf8 = g_locale_to_utf8 (line, -1, NULL, NULL, NULL); if (line_utf8 == NULL) { return VCARD_ENCODING_NONE; } encoding = VCARD_ENCODING_LOCALE; } if (g_ascii_strncasecmp (line_utf8, "BEGIN:VCARD", 11) != 0) { encoding = VCARD_ENCODING_NONE; } g_free (line_utf8); return encoding; } static void primary_selection_changed_cb (ESourceSelector *selector, EImportTarget *target) { g_datalist_set_data_full(&target->data, "vcard-source", g_object_ref (e_source_selector_get_primary_selection (selector)), g_object_unref); } static GtkWidget * vcard_getwidget (EImport *ei, EImportTarget *target, EImportImporter *im) { GtkWidget *vbox, *selector; ESource *primary; ESourceList *source_list; /* FIXME Better error handling */ if (!e_book_client_get_sources (&source_list, NULL)) return NULL; vbox = gtk_vbox_new (FALSE, FALSE); selector = e_source_selector_new (source_list); e_source_selector_show_selection (E_SOURCE_SELECTOR (selector), FALSE); gtk_box_pack_start (GTK_BOX (vbox), selector, FALSE, TRUE, 6); primary = g_datalist_get_data(&target->data, "vcard-source"); if (primary == NULL) { primary = e_source_list_peek_source_any (source_list); g_object_ref (primary); g_datalist_set_data_full ( &target->data, "vcard-source", primary, (GDestroyNotify) g_object_unref); } e_source_selector_set_primary_selection ( E_SOURCE_SELECTOR (selector), primary); g_object_unref (source_list); g_signal_connect ( selector, "primary_selection_changed", G_CALLBACK (primary_selection_changed_cb), target); gtk_widget_show_all (vbox); return vbox; } static gboolean vcard_supported (EImport *ei, EImportTarget *target, EImportImporter *im) { EImportTargetURI *s; gchar *filename; gboolean retval; if (target->type != E_IMPORT_TARGET_URI) return FALSE; s = (EImportTargetURI *) target; if (s->uri_src == NULL) return TRUE; if (strncmp(s->uri_src, "file:///", 8) != 0) return FALSE; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (filename == NULL) return FALSE; retval = (guess_vcard_encoding (filename) != VCARD_ENCODING_NONE); g_free (filename); return retval; } static void vcard_import_done (VCardImporter *gci) { if (gci->idle_id) g_source_remove (gci->idle_id); g_free (gci->contents); g_object_unref (gci->book_client); e_client_util_free_object_slist (gci->contactlist); e_import_complete (gci->import, gci->target); g_object_unref (gci->import); g_free (gci); } static void book_loaded_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { ESource *source = E_SOURCE (source_object); VCardImporter *gci = user_data; EClient *client = NULL; e_client_utils_open_new_finish (source, result, &client, NULL); if (client == NULL) { vcard_import_done (gci); return; } gci->book_client = E_BOOK_CLIENT (client); if (gci->encoding == VCARD_ENCODING_UTF16) { gchar *tmp; gunichar2 *contents_utf16 = (gunichar2*) gci->contents; tmp = utf16_to_utf8 (contents_utf16); g_free (gci->contents); gci->contents = tmp; } else if (gci->encoding == VCARD_ENCODING_LOCALE) { gchar *tmp; tmp = g_locale_to_utf8 (gci->contents, -1, NULL, NULL, NULL); g_free (gci->contents); gci->contents = tmp; } gci->contactlist = eab_contact_list_from_string (gci->contents); g_free (gci->contents); gci->contents = NULL; gci->iterator = gci->contactlist; gci->total = g_slist_length (gci->contactlist); if (gci->iterator) gci->idle_id = g_idle_add (vcard_import_contacts, gci); else vcard_import_done (gci); } static void vcard_import (EImport *ei, EImportTarget *target, EImportImporter *im) { VCardImporter *gci; ESource *source; EImportTargetURI *s = (EImportTargetURI *) target; gchar *filename; gchar *contents; VCardEncoding encoding; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (filename == NULL) { g_message(G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src); e_import_complete (ei, target); return; } encoding = guess_vcard_encoding (filename); if (encoding == VCARD_ENCODING_NONE) { g_free (filename); /* This check is superfluous, we've already * checked otherwise we can't get here ... */ e_import_complete (ei, target); return; } if (!g_file_get_contents (filename, &contents, NULL, NULL)) { g_message (G_STRLOC ":Couldn't read file."); g_free (filename); e_import_complete (ei, target); return; } g_free (filename); gci = g_malloc0 (sizeof (*gci)); g_datalist_set_data(&target->data, "vcard-data", gci); gci->import = g_object_ref (ei); gci->target = target; gci->encoding = encoding; gci->contents = contents; source = g_datalist_get_data (&target->data, "vcard-source"); e_client_utils_open_new (source, E_CLIENT_SOURCE_TYPE_CONTACTS, FALSE, NULL, e_client_utils_authenticate_handler, NULL, book_loaded_cb, gci); } static void vcard_cancel (EImport *ei, EImportTarget *target, EImportImporter *im) { VCardImporter *gci = g_datalist_get_data(&target->data, "vcard-data"); if (gci) gci->state = 1; } static GtkWidget * vcard_get_preview (EImport *ei, EImportTarget *target, EImportImporter *im) { GtkWidget *preview; GSList *contacts; gchar *contents; VCardEncoding encoding; EImportTargetURI *s = (EImportTargetURI *) target; gchar *filename; filename = g_filename_from_uri (s->uri_src, NULL, NULL); if (filename == NULL) { g_message (G_STRLOC ": Couldn't get filename from URI '%s'", s->uri_src); return NULL; } encoding = guess_vcard_encoding (filename); if (encoding == VCARD_ENCODING_NONE) { g_free (filename); return NULL; } if (!g_file_get_contents (filename, &contents, NULL, NULL)) { g_message (G_STRLOC ": Couldn't read file."); g_free (filename); return NULL; } g_free (filename); if (encoding == VCARD_ENCODING_UTF16) { gchar *tmp; gunichar2 *contents_utf16 = (gunichar2 *) contents; tmp = utf16_to_utf8 (contents_utf16); g_free (contents); contents = tmp; } else if (encoding == VCARD_ENCODING_LOCALE) { gchar *tmp; tmp = g_locale_to_utf8 (contents, -1, NULL, NULL, NULL); g_free (contents); contents = tmp; } contacts = eab_contact_list_from_string (contents); g_free (contents); preview = evolution_contact_importer_get_preview_widget (contacts); e_client_util_free_object_slist (contacts); return preview; } static EImportImporter vcard_importer = { E_IMPORT_TARGET_URI, 0, vcard_supported, vcard_getwidget, vcard_import, vcard_cancel, vcard_get_preview, }; EImportImporter * evolution_vcard_importer_peek (void) { vcard_importer.name = _("vCard (.vcf, .gcrd)"); vcard_importer.description = _("Evolution vCard Importer"); return &vcard_importer; } /* utility functions shared between all contact importers */ static void preview_contact (EWebViewPreview *preview, EContact *contact) { gint idx; gboolean had_value = FALSE; const gint fields[] = { E_CONTACT_FILE_AS, E_CONTACT_CATEGORIES, E_CONTACT_IS_LIST, E_CONTACT_LIST_SHOW_ADDRESSES, E_CONTACT_WANTS_HTML, E_CONTACT_FULL_NAME, E_CONTACT_GIVEN_NAME, E_CONTACT_FAMILY_NAME, E_CONTACT_NICKNAME, E_CONTACT_SPOUSE, E_CONTACT_BIRTH_DATE, E_CONTACT_ANNIVERSARY, E_CONTACT_MAILER, E_CONTACT_EMAIL, -1, E_CONTACT_ORG, E_CONTACT_ORG_UNIT, E_CONTACT_OFFICE, E_CONTACT_TITLE, E_CONTACT_ROLE, E_CONTACT_MANAGER, E_CONTACT_ASSISTANT, -1, E_CONTACT_PHONE_ASSISTANT, E_CONTACT_PHONE_BUSINESS, E_CONTACT_PHONE_BUSINESS_2, E_CONTACT_PHONE_BUSINESS_FAX, E_CONTACT_PHONE_CALLBACK, E_CONTACT_PHONE_CAR, E_CONTACT_PHONE_COMPANY, E_CONTACT_PHONE_HOME, E_CONTACT_PHONE_HOME_2, E_CONTACT_PHONE_HOME_FAX, E_CONTACT_PHONE_ISDN, E_CONTACT_PHONE_MOBILE, E_CONTACT_PHONE_OTHER, E_CONTACT_PHONE_OTHER_FAX, E_CONTACT_PHONE_PAGER, E_CONTACT_PHONE_PRIMARY, E_CONTACT_PHONE_RADIO, E_CONTACT_PHONE_TELEX, E_CONTACT_PHONE_TTYTDD, -1, E_CONTACT_ADDRESS_HOME, E_CONTACT_ADDRESS_WORK, E_CONTACT_ADDRESS_OTHER, -1, E_CONTACT_HOMEPAGE_URL, E_CONTACT_BLOG_URL, E_CONTACT_CALENDAR_URI, E_CONTACT_FREEBUSY_URL, E_CONTACT_ICS_CALENDAR, E_CONTACT_VIDEO_URL, -1, E_CONTACT_IM_AIM, E_CONTACT_IM_GROUPWISE, E_CONTACT_IM_JABBER, E_CONTACT_IM_YAHOO, E_CONTACT_IM_MSN, E_CONTACT_IM_ICQ, E_CONTACT_IM_GADUGADU, E_CONTACT_IM_SKYPE, -1, E_CONTACT_NOTE }; g_return_if_fail (preview != NULL); g_return_if_fail (contact != NULL); for (idx = 0; idx < G_N_ELEMENTS (fields); idx++) { EContactField field; if (fields[idx] == -1) { if (had_value) e_web_view_preview_add_empty_line (preview); had_value = FALSE; continue; } field = fields[idx]; if (field == E_CONTACT_BIRTH_DATE || field == E_CONTACT_ANNIVERSARY) { EContactDate *dt = e_contact_get (contact, field); if (dt) { GDate gd = { 0 }; struct tm tm; gchar *value; g_date_set_dmy (&gd, dt->day, dt->month, dt->year); g_date_to_struct_tm (&gd, &tm); value = e_datetime_format_format_tm ( "addressbook", "table", DTFormatKindDate, &tm); if (value) { e_web_view_preview_add_section ( preview, e_contact_pretty_name (field), value); had_value = TRUE; } g_free (value); e_contact_date_free (dt); } } else if (field == E_CONTACT_IS_LIST || field == E_CONTACT_WANTS_HTML || field == E_CONTACT_LIST_SHOW_ADDRESSES) { if (e_contact_get (contact, field)) { e_web_view_preview_add_text ( preview, e_contact_pretty_name (field)); had_value = TRUE; } } else if (field == E_CONTACT_ADDRESS_HOME || field == E_CONTACT_ADDRESS_WORK || field == E_CONTACT_ADDRESS_OTHER) { EContactAddress *addr = e_contact_get (contact, field); if (addr) { gboolean have = FALSE; #define add_it(_what) \ if (addr->_what && *addr->_what) { \ e_web_view_preview_add_section ( \ preview, have ? NULL : \ e_contact_pretty_name (field), addr->_what); \ have = TRUE; \ had_value = TRUE; \ } add_it (po); add_it (ext); add_it (street); add_it (locality); add_it (region); add_it (code); add_it (country); #undef add_it e_contact_address_free (addr); } } else if (field == E_CONTACT_IM_AIM || field == E_CONTACT_IM_GROUPWISE || field == E_CONTACT_IM_JABBER || field == E_CONTACT_IM_YAHOO || field == E_CONTACT_IM_MSN || field == E_CONTACT_IM_ICQ || field == E_CONTACT_IM_GADUGADU || field == E_CONTACT_IM_SKYPE || field == E_CONTACT_EMAIL) { GList *attrs, *a; gboolean have = FALSE; const gchar *pretty_name; pretty_name = e_contact_pretty_name (field); attrs = e_contact_get_attributes (contact, field); for (a = attrs; a; a = a->next) { EVCardAttribute *attr = a->data; GList *value; if (!attr) continue; value = e_vcard_attribute_get_values (attr); while (value != NULL) { const gchar *str = value->data; if (str && *str) { e_web_view_preview_add_section ( preview, have ? NULL : pretty_name, str); have = TRUE; had_value = TRUE; } value = value->next; } e_vcard_attribute_free (attr); } g_list_free (attrs); } else if (field == E_CONTACT_CATEGORIES) { const gchar *pretty_name; const gchar *value; pretty_name = e_contact_pretty_name (field); value = e_contact_get_const (contact, field); if (value != NULL && *value != '\0') { e_web_view_preview_add_section ( preview, pretty_name, value); had_value = TRUE; } } else { const gchar *pretty_name; const gchar *value; pretty_name = e_contact_pretty_name (field); value = e_contact_get_const (contact, field); if (value != NULL && *value != '\0') { e_web_view_preview_add_section ( preview, pretty_name, value); had_value = TRUE; } } } } static void preview_selection_changed_cb (GtkTreeSelection *selection, EWebViewPreview *preview) { GtkTreeIter iter; GtkTreeModel *model = NULL; g_return_if_fail (selection != NULL); g_return_if_fail (preview != NULL); e_web_view_preview_begin_update (preview); if (gtk_tree_selection_get_selected (selection, &model, &iter) && model) { EContact *contact = NULL; gtk_tree_model_get (model, &iter, 1, &contact, -1); if (contact) { preview_contact (preview, contact); g_object_unref (contact); } } e_web_view_preview_end_update (preview); } GtkWidget * evolution_contact_importer_get_preview_widget (const GSList *contacts) { GtkWidget *preview; GtkTreeView *tree_view; GtkTreeSelection *selection; GtkListStore *store; GtkTreeIter iter; const GSList *c; if (!contacts) return NULL; store = gtk_list_store_new (2, G_TYPE_STRING, E_TYPE_CONTACT); for (c = contacts; c; c = c->next) { const gchar *description; gchar *free_description = NULL; EContact *contact = (EContact *) c->data; if (!contact || !E_IS_CONTACT (contact)) continue; description = e_contact_get_const (contact, E_CONTACT_FILE_AS); if (!description) description = e_contact_get_const (contact, E_CONTACT_UID); if (!description) description = e_contact_get_const (contact, E_CONTACT_FULL_NAME); if (!description) { description = e_contact_get_const (contact, E_CONTACT_EMAIL_1); if (description) { const gchar *at = strchr (description, '@'); if (at) { free_description = g_strndup ( description, (gsize) (at - description)); description = free_description; } } } gtk_list_store_append (store, &iter); gtk_list_store_set (store, &iter, 0, description ? description : "", 1, contact, -1 ); g_free (free_description); } if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) { g_object_unref (store); return NULL; } preview = e_web_view_preview_new (); gtk_widget_show (preview); tree_view = e_web_view_preview_get_tree_view (E_WEB_VIEW_PREVIEW (preview)); g_return_val_if_fail (tree_view != NULL, NULL); gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (store)); g_object_unref (store); gtk_tree_view_insert_column_with_attributes (tree_view, -1, _("Contact"), gtk_cell_renderer_text_new (), "text", 0, NULL); if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL) > 1) e_web_view_preview_show_tree_view (E_WEB_VIEW_PREVIEW (preview)); selection = gtk_tree_view_get_selection (tree_view); gtk_tree_selection_select_iter (selection, &iter); g_signal_connect ( selection, "changed", G_CALLBACK (preview_selection_changed_cb), preview); preview_selection_changed_cb (selection, E_WEB_VIEW_PREVIEW (preview)); return preview; }