diff --git a/configure.ac b/configure.ac
index 6f96573..5736a4c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -34,6 +34,7 @@ GTK_REQUIRED=2.21.2
 GCONF_REQUIRED=1.2.0
 TELEPATHY_GLIB_REQUIRED=0.11.7
 TELEPATHY_LOGGER=0.1.2
+FOLKS_REQUIRED=0.1.9
 ENCHANT_REQUIRED=1.2.0
 ISO_CODES_REQUIRED=0.35
 LIBNOTIFY_REQUIRED=0.4.4
@@ -141,6 +142,8 @@ AC_SUBST(ERROR_CFLAGS)
 
 PKG_CHECK_MODULES(LIBEMPATHY,
 [
+   folks >= $FOLKS_REQUIRED
+   folks-telepathy >= $FOLKS_REQUIRED
    glib-2.0 >= $GLIB_REQUIRED
    gobject-2.0
    gio-2.0 >= $GLIB_REQUIRED
@@ -155,6 +158,8 @@ PKG_CHECK_MODULES(LIBEMPATHY,
 
 PKG_CHECK_MODULES(LIBEMPATHYGTK,
 [
+   folks >= $FOLKS_REQUIRED
+   folks-telepathy >= $FOLKS_REQUIRED
    glib-2.0 >= $GLIB_REQUIRED
    gobject-2.0
    gio-2.0 >= $GLIB_REQUIRED
@@ -171,6 +176,8 @@ PKG_CHECK_MODULES(LIBEMPATHYGTK,
 
 PKG_CHECK_MODULES(EMPATHY,
 [
+   folks >= $FOLKS_REQUIRED
+   folks-telepathy >= $FOLKS_REQUIRED
    glib-2.0 >= $GLIB_REQUIRED
    gobject-2.0
    gio-2.0 >= $GLIB_REQUIRED
diff --git a/libempathy-gtk/Makefile.am b/libempathy-gtk/Makefile.am
index 43763d5..c84c3c9 100644
--- a/libempathy-gtk/Makefile.am
+++ b/libempathy-gtk/Makefile.am
@@ -50,6 +50,10 @@ libempathy_gtk_handwritten_source =            	\
 	empathy-contact-selector-dialog.c \
 	empathy-contact-widget.c		\
 	empathy-geometry.c			\
+	empathy-individual-dialogs.c		\
+	empathy-individual-menu.c		\
+	empathy-individual-store.c		\
+	empathy-individual-view.c		\
 	empathy-irc-network-dialog.c		\
 	empathy-kludge-label.c			\
 	empathy-log-window.c			\
@@ -97,6 +101,10 @@ libempathy_gtk_headers =			\
 	empathy-contact-widget.h		\
 	empathy-geometry.h			\
 	empathy-images.h			\
+	empathy-individual-dialogs.h		\
+	empathy-individual-menu.h		\
+	empathy-individual-store.h		\
+	empathy-individual-view.h		\
 	empathy-irc-network-dialog.h		\
 	empathy-kludge-label.h			\
 	empathy-log-window.h			\
diff --git a/libempathy-gtk/empathy-contact-list-view.c b/libempathy-gtk/empathy-contact-list-view.c
index c6099d9..0e7c227 100644
--- a/libempathy-gtk/empathy-contact-list-view.c
+++ b/libempathy-gtk/empathy-contact-list-view.c
@@ -1265,7 +1265,7 @@ contact_list_view_search_activate_cb (GtkWidget *search,
 {
 	GtkTreePath *path;
 	GtkTreeViewColumn *focus_column;
- 
+
 	gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
 	if (path != NULL) {
 		gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
diff --git a/libempathy-gtk/empathy-contact-widget.c b/libempathy-gtk/empathy-contact-widget.c
index 0241281..92f6c4b 100644
--- a/libempathy-gtk/empathy-contact-widget.c
+++ b/libempathy-gtk/empathy-contact-widget.c
@@ -559,7 +559,7 @@ contact_widget_cell_toggled (GtkCellRendererToggle *cell,
   GtkListStore *store;
   GtkTreePath *path;
   GtkTreeIter iter;
-  gboolean enabled;
+  gboolean was_enabled;
   gchar *group;
 
   view = GTK_TREE_VIEW (information->treeview_groups);
@@ -570,27 +570,25 @@ contact_widget_cell_toggled (GtkCellRendererToggle *cell,
 
   gtk_tree_model_get_iter (model, &iter, path);
   gtk_tree_model_get (model, &iter,
-      COL_ENABLED, &enabled,
+      COL_ENABLED, &was_enabled,
       COL_NAME, &group,
       -1);
 
-  gtk_list_store_set (store, &iter, COL_ENABLED, !enabled, -1);
+  gtk_list_store_set (store, &iter, COL_ENABLED, !was_enabled, -1);
   gtk_tree_path_free (path);
 
-  if (group)
+  if (group != NULL)
     {
-      if (enabled)
-        {
-          empathy_contact_list_remove_from_group (
-              EMPATHY_CONTACT_LIST (information->manager), information->contact,
-              group);
-        }
-      else
+      FolksIndividual *individual = folks_individual_dup_from_empathy_contact (
+          information->contact);
+
+      if (individual != NULL)
         {
-          empathy_contact_list_add_to_group (
-              EMPATHY_CONTACT_LIST (information->manager), information->contact,
-              group);
+          folks_groups_change_group (FOLKS_GROUPS (individual), group,
+              !was_enabled);
+          g_object_unref (individual);
         }
+
       g_free (group);
     }
 }
@@ -797,6 +795,7 @@ contact_widget_button_group_clicked_cb (GtkButton *button,
   GtkTreeView *view;
   GtkListStore *store;
   GtkTreeIter iter;
+  FolksIndividual *individual;
   const gchar *group;
 
   view = GTK_TREE_VIEW (information->treeview_groups);
@@ -810,9 +809,13 @@ contact_widget_button_group_clicked_cb (GtkButton *button,
       COL_ENABLED, TRUE,
       -1);
 
-  empathy_contact_list_add_to_group (
-      EMPATHY_CONTACT_LIST (information->manager), information->contact,
-      group);
+  individual = folks_individual_dup_from_empathy_contact (information->contact);
+
+  if (individual != NULL)
+    {
+      folks_groups_change_group (FOLKS_GROUPS (individual), group, TRUE);
+      g_object_unref (individual);
+    }
 }
 
 static void
@@ -1334,7 +1337,14 @@ contact_widget_entry_alias_focus_event_cb (GtkEditable *editable,
         }
       else
         {
-          empathy_contact_set_alias (information->contact, alias);
+          FolksIndividual *individual =
+              folks_individual_dup_from_empathy_contact (information->contact);
+
+          if (individual != NULL)
+            {
+              folks_alias_set_alias (FOLKS_ALIAS (individual), alias);
+              g_object_unref (individual);
+            }
         }
     }
 
@@ -1525,14 +1535,18 @@ contact_widget_contact_update (EmpathyContactWidget *information)
 
       if (information->flags & EMPATHY_CONTACT_WIDGET_EDIT_FAVOURITE)
         {
-          gboolean is_favourite;
+          FolksIndividual *individual =
+              folks_individual_dup_from_empathy_contact (information->contact);
 
-          is_favourite = empathy_contact_list_is_favourite (
-              EMPATHY_CONTACT_LIST (information->manager),
-              information->contact);
+          if (individual != NULL)
+            {
+              gboolean is_favourite = folks_favourite_get_is_favourite (
+                  FOLKS_FAVOURITE (individual));
+              contact_widget_favourites_changed_cb (information->manager,
+                  information->contact, is_favourite, information);
 
-          contact_widget_favourites_changed_cb (information->manager,
-              information->contact, is_favourite, information);
+              g_object_unref (individual);
+            }
         }
 
       gtk_widget_show (information->label_alias);
@@ -1658,19 +1672,14 @@ static void
 favourite_toggled_cb (GtkToggleButton *button,
     EmpathyContactWidget *information)
 {
-  gboolean active;
-
-  active = gtk_toggle_button_get_active (button);
+  FolksIndividual *individual = folks_individual_dup_from_empathy_contact (
+      information->contact);
 
-  if (active)
-    {
-      empathy_contact_list_add_to_favourites (
-          EMPATHY_CONTACT_LIST (information->manager), information->contact);
-    }
-  else
+  if (individual != NULL)
     {
-      empathy_contact_list_remove_from_favourites (
-          EMPATHY_CONTACT_LIST (information->manager), information->contact);
+      gboolean active = gtk_toggle_button_get_active (button);
+      folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), active);
+      g_object_unref (individual);
     }
 }
 
diff --git a/libempathy-gtk/empathy-individual-dialogs.c b/libempathy-gtk/empathy-individual-dialogs.c
new file mode 100644
index 0000000..cd5c0f3
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-dialogs.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <gtk/gtk.h>
+#include <glib/gi18n-lib.h>
+
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-individual-dialogs.h"
+#include "empathy-contact-widget.h"
+#include "empathy-ui-utils.h"
+
+static GtkWidget *new_individual_dialog = NULL;
+
+/*
+ *  New contact dialog
+ */
+
+static gboolean
+can_add_contact_to_account (TpAccount *account,
+    gpointer user_data)
+{
+  EmpathyIndividualManager *individual_manager;
+  TpConnection *connection;
+  gboolean result;
+
+  connection = tp_account_get_connection (account);
+  if (connection == NULL)
+    return FALSE;
+
+  individual_manager = empathy_individual_manager_dup_singleton ();
+  result = empathy_individual_manager_get_flags_for_connection (
+    individual_manager, connection) & EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
+  g_object_unref (individual_manager);
+
+  return result;
+}
+
+static void
+new_individual_response_cb (GtkDialog *dialog,
+    gint response,
+    GtkWidget *contact_widget)
+{
+  EmpathyIndividualManager *individual_manager;
+  EmpathyContact *contact;
+
+  individual_manager = empathy_individual_manager_dup_singleton ();
+  contact = empathy_contact_widget_get_contact (contact_widget);
+
+  if (contact && response == GTK_RESPONSE_OK)
+    empathy_individual_manager_add_from_contact (individual_manager, contact);
+
+  new_individual_dialog = NULL;
+  gtk_widget_destroy (GTK_WIDGET (dialog));
+  g_object_unref (individual_manager);
+}
+
+void
+empathy_new_individual_dialog_show (GtkWindow *parent)
+{
+  empathy_new_individual_dialog_show_with_individual (parent, NULL);
+}
+
+void
+empathy_new_individual_dialog_show_with_individual (GtkWindow *parent,
+    FolksIndividual *individual)
+{
+  GtkWidget *dialog;
+  GtkWidget *button;
+  EmpathyContact *contact = NULL;
+  GtkWidget *contact_widget;
+
+  g_return_if_fail (individual == NULL || FOLKS_IS_INDIVIDUAL (individual));
+
+  if (new_individual_dialog)
+    {
+      gtk_window_present (GTK_WINDOW (new_individual_dialog));
+      return;
+    }
+
+  /* Create dialog */
+  dialog = gtk_dialog_new ();
+  gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+  gtk_window_set_title (GTK_WINDOW (dialog), _("New Contact"));
+
+  /* Cancel button */
+  button = gtk_button_new_with_label (GTK_STOCK_CANCEL);
+  gtk_button_set_use_stock (GTK_BUTTON (button), TRUE);
+  gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
+      GTK_RESPONSE_CANCEL);
+  gtk_widget_show (button);
+
+  /* Add button */
+  button = gtk_button_new_with_label (GTK_STOCK_ADD);
+  gtk_button_set_use_stock (GTK_BUTTON (button), TRUE);
+  gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, GTK_RESPONSE_OK);
+  gtk_widget_show (button);
+
+  /* Contact info widget */
+  if (individual != NULL)
+    contact = empathy_contact_dup_from_folks_individual (individual);
+
+  contact_widget = empathy_contact_widget_new (contact,
+      EMPATHY_CONTACT_WIDGET_EDIT_ALIAS |
+      EMPATHY_CONTACT_WIDGET_EDIT_ACCOUNT |
+      EMPATHY_CONTACT_WIDGET_EDIT_ID |
+      EMPATHY_CONTACT_WIDGET_EDIT_GROUPS);
+  gtk_container_set_border_width (GTK_CONTAINER (contact_widget), 8);
+  gtk_box_pack_start (
+      GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+      contact_widget, TRUE, TRUE, 0);
+  empathy_contact_widget_set_account_filter (contact_widget,
+      can_add_contact_to_account, NULL);
+  gtk_widget_show (contact_widget);
+
+  new_individual_dialog = dialog;
+
+  g_signal_connect (dialog, "response", G_CALLBACK (new_individual_response_cb),
+      contact_widget);
+
+  if (parent != NULL)
+    gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+  gtk_widget_show (dialog);
+
+  tp_clear_object (&contact);
+}
diff --git a/libempathy-gtk/empathy-individual-dialogs.h b/libempathy-gtk/empathy-individual-dialogs.h
new file mode 100644
index 0000000..b34a7ab
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-dialogs.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_DIALOGS_H__
+#define __EMPATHY_INDIVIDUAL_DIALOGS_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+G_BEGIN_DECLS
+
+void empathy_new_individual_dialog_show (GtkWindow *parent);
+void empathy_new_individual_dialog_show_with_individual (GtkWindow *parent,
+    FolksIndividual *individual);
+
+G_END_DECLS
+
+#endif /*  __EMPATHY_INDIVIDUAL_DIALOGS_H__ */
diff --git a/libempathy-gtk/empathy-individual-menu.c b/libempathy-gtk/empathy-individual-menu.c
new file mode 100644
index 0000000..fcac824
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-menu.c
@@ -0,0 +1,740 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-logger/log-manager.h>
+
+#include <libempathy/empathy-call-factory.h>
+#include <libempathy/empathy-dispatcher.h>
+#include <libempathy/empathy-contact-manager.h>
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-chatroom-manager.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-individual-menu.h"
+#include "empathy-images.h"
+#include "empathy-log-window.h"
+#include "empathy-contact-dialogs.h"
+#include "empathy-individual-dialogs.h"
+#include "empathy-ui-utils.h"
+#include "empathy-share-my-desktop.h"
+
+GtkWidget *
+empathy_individual_menu_new (FolksIndividual *individual,
+    EmpathyIndividualFeatureFlags features)
+{
+  GtkWidget *menu;
+  GtkMenuShell *shell;
+  GtkWidget *item;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  if (features == EMPATHY_INDIVIDUAL_FEATURE_NONE)
+    return NULL;
+
+  menu = gtk_menu_new ();
+  shell = GTK_MENU_SHELL (menu);
+
+  /* Add Contact */
+  item = empathy_individual_add_menu_item_new (individual);
+  if (item)
+    {
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+  /* Chat */
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
+    {
+      item = empathy_individual_chat_menu_item_new (individual);
+      if (item != NULL)
+        {
+          gtk_menu_shell_append (shell, item);
+          gtk_widget_show (item);
+        }
+    }
+
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
+    {
+      /* Audio Call */
+      item = empathy_individual_audio_call_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+
+      /* Video Call */
+      item = empathy_individual_video_call_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+  /* Log */
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
+    {
+      item = empathy_individual_log_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+  /* Invite */
+  item = empathy_individual_invite_menu_item_new (individual);
+  gtk_menu_shell_append (shell, item);
+  gtk_widget_show (item);
+
+  /* File transfer */
+  item = empathy_individual_file_transfer_menu_item_new (individual);
+  gtk_menu_shell_append (shell, item);
+  gtk_widget_show (item);
+
+  /* Share my desktop */
+  /* FIXME we should add the "Share my desktop" menu item if Vino is
+  a registered handler in MC5 */
+  item = empathy_individual_share_my_desktop_menu_item_new (individual);
+  gtk_menu_shell_append (shell, item);
+  gtk_widget_show (item);
+
+  /* Separator */
+  if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
+      EMPATHY_INDIVIDUAL_FEATURE_INFO |
+      EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE))
+    {
+      item = gtk_separator_menu_item_new ();
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+  /* Edit */
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
+    {
+      item = empathy_individual_edit_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+  /* Info */
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
+    {
+      item = empathy_individual_info_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+
+#if HAVE_FAVOURITE_CONTACTS
+  /* Favorite checkbox */
+  if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
+    {
+      item = empathy_individual_favourite_menu_item_new (individual);
+      gtk_menu_shell_append (shell, item);
+      gtk_widget_show (item);
+    }
+#endif
+
+  return menu;
+}
+
+static void
+empathy_individual_add_menu_item_activated (GtkMenuItem *item,
+  FolksIndividual *individual)
+{
+  GtkWidget *toplevel;
+
+  toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
+  if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
+    toplevel = NULL;
+
+  empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
+      individual);
+}
+
+GtkWidget *
+empathy_individual_add_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyIndividualManager *manager;
+  EmpathyContact *contact = NULL;
+  TpConnection *connection;
+  GList *l, *members;
+  gboolean found = FALSE;
+  EmpathyIndividualManagerFlags flags;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  if (!empathy_individual_manager_initialized ())
+    {
+      item = NULL;
+      goto out;
+    }
+
+  manager = empathy_individual_manager_dup_singleton ();
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  connection = empathy_contact_get_connection (contact);
+
+  flags = empathy_individual_manager_get_flags_for_connection (manager,
+      connection);
+
+  if (!(flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD))
+    {
+      item = NULL;
+      goto out;
+    }
+
+  members = empathy_individual_manager_get_members (
+      EMPATHY_INDIVIDUAL_MANAGER (manager));
+
+  for (l = members; l && !found; l = l->next)
+    {
+      if (!tp_strdiff (folks_individual_get_id (l->data),
+              folks_individual_get_id (individual)))
+        {
+          found = TRUE;
+        }
+    }
+  g_list_free (members);
+  g_object_unref (manager);
+
+  if (found)
+    {
+      item = NULL;
+      goto out;
+    }
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
+  image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+  g_signal_connect (item, "activate",
+      G_CALLBACK (empathy_individual_add_menu_item_activated),
+      individual);
+
+out:
+  tp_clear_object (&contact);
+
+  return item;
+}
+
+static void
+empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
+  FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (contact != NULL);
+
+  empathy_dispatcher_chat_with_contact (contact, gtk_get_current_event_time (),
+      NULL, NULL);
+
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_chat_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+  g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
+      NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
+      GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  g_signal_connect (item, "activate",
+      G_CALLBACK (empathy_individual_chat_menu_item_activated), individual);
+
+  return item;
+}
+
+static void
+empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
+  FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+  EmpathyCallFactory *factory;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  factory = empathy_call_factory_get ();
+  empathy_call_factory_new_call_with_streams (contact, TRUE, FALSE,
+      gtk_get_current_event_time (), NULL, NULL);
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_audio_call_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_set_sensitive (item, empathy_contact_can_voip_audio (contact));
+  gtk_widget_show (image);
+
+  g_signal_connect (item, "activate",
+      G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
+      individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+static void
+empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
+  FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+  EmpathyCallFactory *factory;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  factory = empathy_call_factory_get ();
+  empathy_call_factory_new_call_with_streams (contact, TRUE, TRUE,
+      gtk_get_current_event_time (), NULL, NULL);
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_video_call_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
+      GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_set_sensitive (item, empathy_contact_can_voip_video (contact));
+  gtk_widget_show (image);
+
+  g_signal_connect (item, "activate",
+      G_CALLBACK (empathy_individual_video_call_menu_item_activated),
+      individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+static void
+individual_log_menu_item_activate_cb (FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  empathy_log_window_show (empathy_contact_get_account (contact),
+      empathy_contact_get_id (contact), FALSE, NULL);
+
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_log_menu_item_new (FolksIndividual *individual)
+{
+  TplLogManager *manager;
+  gboolean have_log;
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  manager = tpl_log_manager_dup_singleton ();
+  have_log = tpl_log_manager_exists (manager,
+      empathy_contact_get_account (contact), empathy_contact_get_id (contact),
+      FALSE);
+  g_object_unref (manager);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_set_sensitive (item, have_log);
+  gtk_widget_show (image);
+
+  g_signal_connect_swapped (item, "activate",
+      G_CALLBACK (individual_log_menu_item_activate_cb), individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+static void
+individual_file_transfer_menu_item_activate_cb (FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  empathy_send_file_with_file_chooser (contact);
+
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
+                GTK_ICON_SIZE_MENU);
+  gtk_widget_set_sensitive (item, empathy_contact_can_send_files (contact));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  g_signal_connect_swapped (item, "activate",
+      G_CALLBACK (individual_file_transfer_menu_item_activate_cb), individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+static void
+individual_share_my_desktop_menu_item_activate_cb (FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  empathy_share_my_desktop_share_with_contact (contact);
+
+  g_object_unref (contact);
+}
+
+GtkWidget *
+empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
+  image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
+  gtk_widget_set_sensitive (item,
+      empathy_contact_can_use_rfb_stream_tube (contact));
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  g_signal_connect_swapped (item, "activate",
+      G_CALLBACK (individual_share_my_desktop_menu_item_activate_cb),
+      individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+static void
+favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
+  FolksIndividual *individual)
+{
+  folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual),
+      gtk_check_menu_item_get_active (item));
+}
+
+GtkWidget *
+empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+
+  item = gtk_check_menu_item_new_with_label (_("Favorite"));
+
+  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+      folks_favourite_get_is_favourite (FOLKS_FAVOURITE (individual)));
+
+  g_signal_connect (item, "toggled",
+      G_CALLBACK (favourite_menu_item_toggled_cb), individual);
+
+  return item;
+}
+
+static void
+individual_info_menu_item_activate_cb (FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  empathy_contact_information_dialog_show (contact, NULL);
+
+  tp_clear_object (&contact);
+}
+
+GtkWidget *
+empathy_individual_info_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+  g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
+      NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
+                GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  g_signal_connect_swapped (item, "activate",
+          G_CALLBACK (individual_info_menu_item_activate_cb),
+          individual);
+
+  return item;
+}
+
+static void
+individual_edit_menu_item_activate_cb (FolksIndividual *individual)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  empathy_contact_edit_dialog_show (contact, NULL);
+
+  tp_clear_object (&contact);
+}
+
+GtkWidget *
+empathy_individual_edit_menu_item_new (FolksIndividual *individual)
+{
+  EmpathyIndividualManager *manager;
+  GtkWidget *item;
+  GtkWidget *image;
+  gboolean enable = FALSE;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  if (empathy_individual_manager_initialized ())
+    {
+      TpConnection *connection;
+      EmpathyIndividualManagerFlags flags;
+
+      manager = empathy_individual_manager_dup_singleton ();
+      connection = empathy_contact_get_connection (contact);
+      flags = empathy_individual_manager_get_flags_for_connection (
+          manager, connection);
+
+      enable = (flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS ||
+                flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP);
+
+      g_object_unref (manager);
+    }
+
+  item = gtk_image_menu_item_new_with_mnemonic (
+      C_("Edit individual (contextual menu)", "_Edit"));
+  image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+  gtk_widget_show (image);
+
+  gtk_widget_set_sensitive (item, enable);
+
+  g_signal_connect_swapped (item, "activate",
+      G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
+
+  g_object_unref (contact);
+
+  return item;
+}
+
+typedef struct
+{
+  FolksIndividual *individual;
+  EmpathyChatroom *chatroom;
+} RoomSubMenuData;
+
+static RoomSubMenuData *
+room_sub_menu_data_new (FolksIndividual *individual,
+      EmpathyChatroom *chatroom)
+{
+  RoomSubMenuData *data;
+
+  data = g_slice_new (RoomSubMenuData);
+  data->individual = g_object_ref (individual);
+  data->chatroom = g_object_ref (chatroom);
+  return data;
+}
+
+static void
+room_sub_menu_data_free (RoomSubMenuData *data)
+{
+  g_object_unref (data->individual);
+  g_object_unref (data->chatroom);
+  g_slice_free (RoomSubMenuData, data);
+}
+
+static void
+room_sub_menu_activate_cb (GtkWidget *item,
+         RoomSubMenuData *data)
+{
+  EmpathyTpChat *chat;
+  EmpathyContact *contact;
+
+  chat = empathy_chatroom_get_tp_chat (data->chatroom);
+  if (chat == NULL)
+    {
+      /* channel was invalidated. Ignoring */
+      return;
+    }
+
+  contact = empathy_contact_dup_from_folks_individual (data->individual);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  /* send invitation */
+  empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat),
+      contact, _("Inviting you to this room"));
+
+  g_object_unref (contact);
+}
+
+static GtkWidget *
+create_room_sub_menu (FolksIndividual *individual,
+                      EmpathyChatroom *chatroom)
+{
+  GtkWidget *item;
+  RoomSubMenuData *data;
+
+  item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
+  data = room_sub_menu_data_new (individual, chatroom);
+  g_signal_connect_data (item, "activate",
+      G_CALLBACK (room_sub_menu_activate_cb), data,
+      (GClosureNotify) room_sub_menu_data_free, 0);
+
+  return item;
+}
+
+GtkWidget *
+empathy_individual_invite_menu_item_new (FolksIndividual *individual)
+{
+  GtkWidget *item;
+  GtkWidget *image;
+  GtkWidget *room_item;
+  EmpathyChatroomManager *mgr;
+  GList *rooms, *l;
+  GtkWidget *submenu = NULL;
+  EmpathyContact *contact;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
+  image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
+      GTK_ICON_SIZE_MENU);
+  gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+
+  mgr = empathy_chatroom_manager_dup_singleton (NULL);
+  rooms = empathy_chatroom_manager_get_chatrooms (mgr,
+      empathy_contact_get_account (contact));
+
+  for (l = rooms; l != NULL; l = g_list_next (l))
+    {
+      EmpathyChatroom *chatroom = l->data;
+
+      if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
+        {
+          if (G_UNLIKELY (submenu == NULL))
+            submenu = gtk_menu_new ();
+
+          room_item = create_room_sub_menu (individual, chatroom);
+          gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
+          gtk_widget_show (room_item);
+        }
+    }
+
+  if (submenu)
+    gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+  else
+    gtk_widget_set_sensitive (item, FALSE);
+
+  gtk_widget_show (image);
+
+  g_object_unref (contact);
+  g_object_unref (mgr);
+  g_list_free (rooms);
+
+  return item;
+}
diff --git a/libempathy-gtk/empathy-individual-menu.h b/libempathy-gtk/empathy-individual-menu.h
new file mode 100644
index 0000000..37b93ea
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-menu.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2008-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_MENU_H__
+#define __EMPATHY_INDIVIDUAL_MENU_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+typedef enum {
+	EMPATHY_INDIVIDUAL_FEATURE_NONE = 0,
+	EMPATHY_INDIVIDUAL_FEATURE_CHAT = 1 << 0,
+	EMPATHY_INDIVIDUAL_FEATURE_CALL = 1 << 1,
+	EMPATHY_INDIVIDUAL_FEATURE_LOG = 1 << 2,
+	EMPATHY_INDIVIDUAL_FEATURE_EDIT = 1 << 3,
+	EMPATHY_INDIVIDUAL_FEATURE_INFO = 1 << 4,
+	EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE = 1 << 5,
+	EMPATHY_INDIVIDUAL_FEATURE_ALL = (1 << 6) - 1,
+} EmpathyIndividualFeatureFlags;
+
+GtkWidget * empathy_individual_menu_new (FolksIndividual *individual,
+    EmpathyIndividualFeatureFlags features);
+GtkWidget * empathy_individual_add_menu_item_new  (FolksIndividual *individual);
+GtkWidget * empathy_individual_chat_menu_item_new (FolksIndividual *individual);
+GtkWidget * empathy_individual_audio_call_menu_item_new (
+    FolksIndividual *individual);
+GtkWidget * empathy_individual_video_call_menu_item_new (
+    FolksIndividual *individual);
+GtkWidget * empathy_individual_log_menu_item_new  (FolksIndividual *individual);
+GtkWidget * empathy_individual_info_menu_item_new (FolksIndividual *individual);
+GtkWidget * empathy_individual_edit_menu_item_new (FolksIndividual *individual);
+GtkWidget * empathy_individual_invite_menu_item_new (
+    FolksIndividual *individual);
+GtkWidget * empathy_individual_file_transfer_menu_item_new (
+    FolksIndividual *individual);
+GtkWidget * empathy_individual_share_my_desktop_menu_item_new (
+    FolksIndividual *individual);
+GtkWidget * empathy_individual_favourite_menu_item_new (
+    FolksIndividual *individual);
+
+G_END_DECLS
+
+#endif /* __EMPATHY_INDIVIDUAL_MENU_H__ */
+
diff --git a/libempathy-gtk/empathy-individual-store.c b/libempathy-gtk/empathy-individual-store.c
new file mode 100644
index 0000000..84fcbe2
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-store.c
@@ -0,0 +1,1867 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ *          Martyn Russell <martyn@imendio.com>
+ *          Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+#include <telepathy-glib/util.h>
+
+#include <libempathy/empathy-utils.h>
+#include <libempathy/empathy-enum-types.h>
+#include <libempathy/empathy-individual-manager.h>
+
+#include "empathy-individual-store.h"
+#include "empathy-ui-utils.h"
+#include "empathy-gtk-enum-types.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
+#include <libempathy/empathy-debug.h>
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+/* Time in seconds user is shown as active */
+#define ACTIVE_USER_SHOW_TIME 7
+
+/* Time in seconds after connecting which we wait before active users are enabled */
+#define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualStore)
+typedef struct
+{
+  EmpathyIndividualManager *manager;
+  gboolean show_offline;
+  gboolean show_avatars;
+  gboolean show_groups;
+  gboolean is_compact;
+  gboolean show_protocols;
+  gboolean show_active;
+  EmpathyIndividualStoreSort sort_criterium;
+  guint inhibit_active;
+  guint setup_idle_id;
+  gboolean dispose_has_run;
+  GHashTable *status_icons;
+} EmpathyIndividualStorePriv;
+
+typedef struct
+{
+  GtkTreeIter iter;
+  const gchar *name;
+  gboolean found;
+} FindGroup;
+
+typedef struct
+{
+  FolksIndividual *individual;
+  gboolean found;
+  GList *iters;
+} FindContact;
+
+typedef struct
+{
+  EmpathyIndividualStore *self;
+  FolksIndividual *individual;
+  gboolean remove;
+} ShowActiveData;
+
+enum
+{
+  PROP_0,
+  PROP_INDIVIDUAL_MANAGER,
+  PROP_SHOW_OFFLINE,
+  PROP_SHOW_AVATARS,
+  PROP_SHOW_PROTOCOLS,
+  PROP_SHOW_GROUPS,
+  PROP_IS_COMPACT,
+  PROP_SORT_CRITERIUM
+};
+
+/* prototypes to break cycles */
+static void individual_store_contact_update (EmpathyIndividualStore *self,
+    FolksIndividual *individual);
+
+G_DEFINE_TYPE (EmpathyIndividualStore, empathy_individual_store,
+    GTK_TYPE_TREE_STORE);
+
+static void
+add_individual_to_store (GtkTreeStore *self,
+    GtkTreeIter *iter,
+    GtkTreeIter *parent,
+    FolksIndividual *individual,
+    EmpathyIndividualManagerFlags flags)
+{
+  EmpathyContact *contact;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  gtk_tree_store_insert_with_values (self, iter, parent, 0,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME,
+      folks_individual_get_alias (individual),
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, individual,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+      EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, flags,
+      -1);
+
+  if (contact != NULL)
+    {
+      gtk_tree_store_set (GTK_TREE_STORE (self), iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL,
+            empathy_contact_get_capabilities (contact) &
+              EMPATHY_CAPABILITIES_AUDIO,
+          EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
+            empathy_contact_get_capabilities (contact) &
+              EMPATHY_CAPABILITIES_VIDEO,
+          -1);
+    }
+
+  tp_clear_object (&contact);
+}
+
+static gboolean
+individual_store_get_group_foreach (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    FindGroup *fg)
+{
+  gchar *str;
+  gboolean is_group;
+
+  /* Groups are only at the top level. */
+  if (gtk_tree_path_get_depth (path) != 1)
+    return FALSE;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &str,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
+
+  if (is_group && !tp_strdiff (str, fg->name))
+    {
+      fg->found = TRUE;
+      fg->iter = *iter;
+    }
+
+  g_free (str);
+
+  return fg->found;
+}
+
+static void
+individual_store_get_group (EmpathyIndividualStore *self,
+    const gchar *name,
+    GtkTreeIter *iter_group_to_set,
+    GtkTreeIter *iter_separator_to_set,
+    gboolean *created,
+    gboolean is_fake_group)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+  GtkTreeIter iter_group;
+  GtkTreeIter iter_separator;
+  FindGroup fg;
+
+  priv = GET_PRIV (self);
+
+  memset (&fg, 0, sizeof (fg));
+
+  fg.name = name;
+
+  model = GTK_TREE_MODEL (self);
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc) individual_store_get_group_foreach, &fg);
+
+  if (!fg.found)
+    {
+      if (created)
+        *created = TRUE;
+
+      gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_group,
+          NULL, 0,
+          EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, NULL,
+          EMPATHY_INDIVIDUAL_STORE_COL_NAME, name,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, TRUE,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, FALSE,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, is_fake_group,
+          -1);
+
+      if (iter_group_to_set)
+        *iter_group_to_set = iter_group;
+
+      gtk_tree_store_insert_with_values (GTK_TREE_STORE (self), &iter_separator,
+          &iter_group, 0,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, TRUE,
+          -1);
+
+      if (iter_separator_to_set)
+        *iter_separator_to_set = iter_separator;
+    }
+  else
+    {
+      if (created)
+        *created = FALSE;
+
+      if (iter_group_to_set)
+        *iter_group_to_set = fg.iter;
+
+      iter_separator = fg.iter;
+
+      if (gtk_tree_model_iter_next (model, &iter_separator))
+        {
+          gboolean is_separator;
+
+          gtk_tree_model_get (model, &iter_separator,
+              EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+          if (is_separator && iter_separator_to_set)
+            *iter_separator_to_set = iter_separator;
+        }
+    }
+}
+
+static gboolean
+individual_store_find_contact_foreach (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    FindContact *fc)
+{
+  FolksIndividual *individual;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  if (individual == fc->individual)
+    {
+      fc->found = TRUE;
+      fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
+    }
+
+  tp_clear_object (&individual);
+
+  return FALSE;
+}
+
+static GList *
+individual_store_find_contact (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+  GList *l = NULL;
+  FindContact fc;
+
+  priv = GET_PRIV (self);
+
+  memset (&fc, 0, sizeof (fc));
+
+  fc.individual = individual;
+
+  model = GTK_TREE_MODEL (self);
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc) individual_store_find_contact_foreach, &fc);
+
+  if (fc.found)
+    l = fc.iters;
+
+  return l;
+}
+
+static void
+individual_store_remove_individual (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+  GList *iters, *l;
+
+  priv = GET_PRIV (self);
+
+  iters = individual_store_find_contact (self, individual);
+  if (iters == NULL)
+    return;
+
+  /* Clean up model */
+  model = GTK_TREE_MODEL (self);
+
+  for (l = iters; l; l = l->next)
+    {
+      GtkTreeIter parent;
+
+      /* NOTE: it is only <= 2 here because we have
+       * separators after the group name, otherwise it
+       * should be 1.
+       */
+      if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
+          gtk_tree_model_iter_n_children (model, &parent) <= 2)
+        {
+          gtk_tree_store_remove (GTK_TREE_STORE (self), &parent);
+        }
+      else
+        {
+          gtk_tree_store_remove (GTK_TREE_STORE (self), l->data);
+        }
+    }
+
+  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (iters);
+}
+
+static void
+individual_store_add_individual (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeIter iter;
+  GHashTable *group_set = NULL;
+  GList *groups = NULL, *l;
+  EmpathyIndividualManager *manager;
+  EmpathyContact *contact;
+  TpConnection *connection;
+  EmpathyIndividualManagerFlags flags = 0;
+
+  priv = GET_PRIV (self);
+
+  if (EMP_STR_EMPTY (folks_individual_get_alias (individual)) ||
+      (!priv->show_offline && !folks_individual_is_online (individual)))
+    return;
+
+  if (priv->show_groups)
+    {
+      group_set = folks_individual_get_groups (individual);
+      groups = g_hash_table_get_keys (group_set);
+    }
+
+  manager = empathy_individual_manager_dup_singleton ();
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  connection = empathy_contact_get_connection (contact);
+  flags = empathy_individual_manager_get_flags_for_connection (manager,
+      connection);
+
+  if (groups == NULL)
+    {
+      GtkTreeIter iter_group, *parent;
+
+      parent = &iter_group;
+
+      /* TODO: implement */
+      DEBUG ("forcing the People Nearby group even when 'show "
+          "groups' is off is unimplemented");
+
+      if (!priv->show_groups)
+        parent = NULL;
+      else
+        {
+          individual_store_get_group (self,
+              EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
+              &iter_group, NULL, NULL, TRUE);
+        }
+
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, parent,
+          individual, flags);
+    }
+
+  /* Else add to each group. */
+  for (l = groups; l; l = l->next)
+    {
+      GtkTreeIter iter_group;
+
+      individual_store_get_group (self, l->data, &iter_group, NULL, NULL,
+          FALSE);
+
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+          individual, flags);
+    }
+  g_list_free (groups);
+  if (group_set != NULL)
+    g_hash_table_unref (group_set);
+
+  if (priv->show_groups &&
+      folks_favourite_get_is_favourite (FOLKS_FAVOURITE (individual)))
+    {
+      /* Add contact to the fake 'Favorites' group */
+      GtkTreeIter iter_group;
+
+      individual_store_get_group (self, EMPATHY_INDIVIDUAL_STORE_FAVORITE,
+          &iter_group, NULL, NULL, TRUE);
+
+      add_individual_to_store (GTK_TREE_STORE (self), &iter, &iter_group,
+          individual, flags);
+    }
+
+  individual_store_contact_update (self, individual);
+
+  tp_clear_object (&contact);
+}
+
+static void
+individual_store_contact_set_active (EmpathyIndividualStore *self,
+    FolksIndividual *individual,
+    gboolean active,
+    gboolean set_changed)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+  GList *iters, *l;
+
+  priv = GET_PRIV (self);
+  model = GTK_TREE_MODEL (self);
+
+  iters = individual_store_find_contact (self, individual);
+  for (l = iters; l; l = l->next)
+    {
+      GtkTreePath *path;
+
+      gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, active,
+          -1);
+
+      DEBUG ("Set item %s", active ? "active" : "inactive");
+
+      if (set_changed)
+        {
+          path = gtk_tree_model_get_path (model, l->data);
+          gtk_tree_model_row_changed (model, path, l->data);
+          gtk_tree_path_free (path);
+        }
+    }
+
+  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (iters);
+
+}
+
+static ShowActiveData *
+individual_store_contact_active_new (EmpathyIndividualStore *self,
+    FolksIndividual *individual,
+    gboolean remove_)
+{
+  ShowActiveData *data;
+
+  DEBUG ("Individual'%s' now active, and %s be removed",
+      folks_individual_get_alias (individual), remove_ ? "WILL" : "WILL NOT");
+
+  data = g_slice_new0 (ShowActiveData);
+
+  data->self = g_object_ref (self);
+  data->individual = g_object_ref (individual);
+  data->remove = remove_;
+
+  return data;
+}
+
+static void
+individual_store_contact_active_free (ShowActiveData *data)
+{
+  g_object_unref (data->individual);
+  g_object_unref (data->self);
+
+  g_slice_free (ShowActiveData, data);
+}
+
+static gboolean
+individual_store_contact_active_cb (ShowActiveData *data)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (data->self);
+
+  if (data->remove &&
+      !priv->show_offline && !folks_individual_is_online (data->individual))
+    {
+      DEBUG ("Individual'%s' active timeout, removing item",
+          folks_individual_get_alias (data->individual));
+      individual_store_remove_individual (data->self, data->individual);
+    }
+
+  DEBUG ("Individual'%s' no longer active",
+      folks_individual_get_alias (data->individual));
+
+  individual_store_contact_set_active (data->self,
+      data->individual, FALSE, TRUE);
+
+  individual_store_contact_active_free (data);
+
+  return FALSE;
+}
+
+static void
+individual_avatar_pixbuf_received_cb (GObject *object,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  FolksIndividual *individual = FOLKS_INDIVIDUAL (object);
+  EmpathyIndividualStore *self = user_data;
+  GError *error = NULL;
+  GdkPixbuf *pixbuf;
+
+  pixbuf = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
+      result, &error);
+
+  if (error != NULL)
+    {
+      DEBUG ("failed to retrieve pixbuf for individual %s: %s",
+          folks_individual_get_alias (individual),
+          error->message);
+      g_clear_error (&error);
+    }
+  else
+    {
+      GList *iters, *l;
+
+      iters = individual_store_find_contact (self, individual);
+      for (l = iters; l; l = l->next)
+        {
+          gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+              EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, pixbuf,
+              -1);
+        }
+    }
+}
+
+static void
+individual_store_contact_update (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualStorePriv *priv;
+  ShowActiveData *data;
+  GtkTreeModel *model;
+  EmpathyContact *contact;
+  GList *iters, *l;
+  gboolean in_list;
+  gboolean should_be_in_list;
+  gboolean was_online = TRUE;
+  gboolean now_online = FALSE;
+  gboolean set_model = FALSE;
+  gboolean do_remove = FALSE;
+  gboolean do_set_active = FALSE;
+  gboolean do_set_refresh = FALSE;
+  gboolean show_avatar = FALSE;
+  GdkPixbuf *pixbuf_status;
+
+  priv = GET_PRIV (self);
+
+  model = GTK_TREE_MODEL (self);
+  contact = empathy_contact_dup_from_folks_individual (individual);
+
+  iters = individual_store_find_contact (self, individual);
+  if (!iters)
+    {
+      in_list = FALSE;
+    }
+  else
+    {
+      in_list = TRUE;
+    }
+
+  /* Get online state now. */
+  now_online = folks_individual_is_online (individual);
+
+  if (priv->show_offline || now_online)
+    {
+      should_be_in_list = TRUE;
+    }
+  else
+    {
+      should_be_in_list = FALSE;
+    }
+
+  if (!in_list && !should_be_in_list)
+    {
+      /* Nothing to do. */
+      DEBUG ("Individual:'%s' in list:NO, should be:NO",
+          folks_individual_get_alias (individual));
+
+      g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+      g_list_free (iters);
+      return;
+    }
+  else if (in_list && !should_be_in_list)
+    {
+      DEBUG ("Individual:'%s' in list:YES, should be:NO",
+          folks_individual_get_alias (individual));
+
+      if (priv->show_active)
+        {
+          do_remove = TRUE;
+          do_set_active = TRUE;
+          do_set_refresh = TRUE;
+
+          set_model = TRUE;
+          DEBUG ("Remove item (after timeout)");
+        }
+      else
+        {
+          DEBUG ("Remove item (now)!");
+          individual_store_remove_individual (self, individual);
+        }
+    }
+  else if (!in_list && should_be_in_list)
+    {
+      DEBUG ("Individual'%s' in list:NO, should be:YES",
+          folks_individual_get_alias (individual));
+
+      individual_store_add_individual (self, individual);
+
+      if (priv->show_active)
+        {
+          do_set_active = TRUE;
+
+          DEBUG ("Set active (individual added)");
+        }
+    }
+  else
+    {
+      DEBUG ("Individual'%s' in list:YES, should be:YES",
+          folks_individual_get_alias (individual));
+
+      /* Get online state before. */
+      if (iters && g_list_length (iters) > 0)
+        {
+          gtk_tree_model_get (model, iters->data,
+              EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, &was_online, -1);
+        }
+
+      /* Is this really an update or an online/offline. */
+      if (priv->show_active)
+        {
+          if (was_online != now_online)
+            {
+              do_set_active = TRUE;
+              do_set_refresh = TRUE;
+
+              DEBUG ("Set active (individual updated %s)",
+                  was_online ? "online  -> offline" : "offline -> online");
+            }
+          else
+            {
+              /* Was TRUE for presence updates. */
+              /* do_set_active = FALSE;  */
+              do_set_refresh = TRUE;
+
+              DEBUG ("Set active (individual updated)");
+            }
+        }
+
+      set_model = TRUE;
+    }
+
+  if (priv->show_avatars && !priv->is_compact)
+    {
+      show_avatar = TRUE;
+    }
+
+  empathy_pixbuf_avatar_from_individual_scaled_async (individual,
+      individual_avatar_pixbuf_received_cb, 32, 32, self);
+
+  pixbuf_status =
+      empathy_individual_store_get_individual_status_icon (self, individual);
+
+  for (l = iters; l && set_model; l = l->next)
+    {
+      gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+          EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+          EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+          EMPATHY_INDIVIDUAL_STORE_COL_NAME,
+            folks_individual_get_alias (individual),
+          EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+            folks_individual_get_presence_type (individual),
+          EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
+            folks_individual_get_presence_message (individual),
+          EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, priv->is_compact,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, FALSE,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE, now_online,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, FALSE,
+          -1);
+
+      if (contact != NULL)
+        {
+          gtk_tree_store_set (GTK_TREE_STORE (self), l->data,
+              EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL,
+                empathy_contact_get_capabilities (contact) &
+                  EMPATHY_CAPABILITIES_AUDIO,
+              EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
+                empathy_contact_get_capabilities (contact) &
+                  EMPATHY_CAPABILITIES_VIDEO,
+              -1);
+        }
+    }
+
+  if (priv->show_active && do_set_active)
+    {
+      individual_store_contact_set_active (self, individual, do_set_active,
+          do_set_refresh);
+
+      if (do_set_active)
+        {
+          data =
+              individual_store_contact_active_new (self, individual,
+              do_remove);
+          g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
+              (GSourceFunc) individual_store_contact_active_cb, data);
+        }
+    }
+
+  /* FIXME: when someone goes online then offline quickly, the
+   * first timeout sets the user to be inactive and the second
+   * timeout removes the user from the contact list, really we
+   * should remove the first timeout.
+   */
+  g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
+  g_list_free (iters);
+  tp_clear_object (&contact);
+}
+
+static void
+individual_store_contact_updated_cb (FolksIndividual *individual,
+    GParamSpec *param,
+    EmpathyIndividualStore *self)
+{
+  DEBUG ("Individual'%s' updated, checking roster is in sync...",
+      folks_individual_get_alias (individual));
+
+  individual_store_contact_update (self, individual);
+}
+
+static void
+individual_store_add_individual_and_connect (EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  g_signal_connect (individual, "notify::avatar",
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+  g_signal_connect (individual, "notify::presence-type",
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+  g_signal_connect (individual, "notify::presence-message",
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+  g_signal_connect (individual, "notify::alias",
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+  g_signal_connect (individual, "notify::capabilities",
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+
+  individual_store_add_individual (self, individual);
+}
+
+static void
+individual_store_remove_individual_and_disconnect (
+    EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  g_signal_handlers_disconnect_by_func (individual,
+      G_CALLBACK (individual_store_contact_updated_cb), self);
+
+  individual_store_remove_individual (self, individual);
+}
+
+static void
+individual_store_members_changed_cb (EmpathyIndividualManager *manager,
+    gchar *message,
+    GList *added,
+    GList *removed,
+    guint reason,
+    EmpathyIndividualStore *self)
+{
+  GList *l;
+
+  for (l = added; l; l = l->next)
+    {
+      DEBUG ("Individual %s %s", folks_individual_get_id (l->data), "added");
+
+      individual_store_add_individual_and_connect (self, l->data);
+    }
+  for (l = removed; l; l = l->next)
+    {
+      DEBUG ("Individual %s %s",
+          folks_individual_get_id (l->data), "removed");
+
+      individual_store_remove_individual_and_disconnect (self, l->data);
+    }
+}
+
+static void
+individual_store_favourites_changed_cb (EmpathyIndividualManager *manager,
+    FolksIndividual *individual,
+    gboolean is_favourite,
+    EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (self);
+
+  DEBUG ("Individual %s is %s a favourite",
+      folks_individual_get_id (individual),
+      is_favourite ? "now" : "no longer");
+
+  individual_store_remove_individual (self, individual);
+  individual_store_add_individual (self, individual);
+}
+
+static void
+individual_store_groups_changed_cb (EmpathyIndividualManager *manager,
+    FolksIndividual *individual,
+    gchar *group,
+    gboolean is_member,
+    EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+  gboolean show_active;
+
+  priv = GET_PRIV (self);
+
+  DEBUG ("Updating groups for individual %s",
+      folks_individual_get_id (individual));
+
+  /* We do this to make sure the groups are correct, if not, we
+   * would have to check the groups already set up for each
+   * contact and then see what has been updated.
+   */
+  show_active = priv->show_active;
+  priv->show_active = FALSE;
+  individual_store_remove_individual (self, individual);
+  individual_store_add_individual (self, individual);
+  priv->show_active = show_active;
+}
+
+static gboolean
+individual_store_manager_setup (gpointer user_data)
+{
+  EmpathyIndividualStore *self = user_data;
+  EmpathyIndividualStorePriv *priv = GET_PRIV (self);
+  GList *individuals;
+
+  /* Signal connection. */
+
+  /* TODO: implement */
+  DEBUG ("handling individual renames unimplemented");
+
+  g_signal_connect (priv->manager,
+      "members-changed",
+      G_CALLBACK (individual_store_members_changed_cb), self);
+
+  g_signal_connect (priv->manager,
+      "favourites-changed",
+      G_CALLBACK (individual_store_favourites_changed_cb), self);
+
+  g_signal_connect (priv->manager,
+      "groups-changed",
+      G_CALLBACK (individual_store_groups_changed_cb), self);
+
+  /* Add contacts already created. */
+  individuals = empathy_individual_manager_get_members (priv->manager);
+  if (individuals != NULL && FOLKS_IS_INDIVIDUAL (individuals->data))
+    {
+      individual_store_members_changed_cb (priv->manager, "initial add",
+          individuals, NULL, 0, self);
+      g_list_free (individuals);
+    }
+
+  priv->setup_idle_id = 0;
+  return FALSE;
+}
+
+static void
+individual_store_set_individual_manager (EmpathyIndividualStore *self,
+    EmpathyIndividualManager *manager)
+{
+  EmpathyIndividualStorePriv *priv = GET_PRIV (self);
+
+  priv->manager = g_object_ref (manager);
+
+  /* Let a chance to have all properties set before populating */
+  priv->setup_idle_id = g_idle_add (individual_store_manager_setup, self);
+}
+
+static void
+individual_store_member_renamed_cb (EmpathyIndividualManager *manager,
+    FolksIndividual *old_individual,
+    FolksIndividual *new_individual,
+    guint reason,
+    gchar *message,
+    EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (self);
+
+  DEBUG ("Individual %s renamed to %s",
+      folks_individual_get_id (old_individual),
+      folks_individual_get_id (new_individual));
+
+  /* add the new contact */
+  individual_store_add_individual_and_connect (self, new_individual);
+
+  /* remove old contact */
+  individual_store_remove_individual_and_disconnect (self, old_individual);
+}
+
+static void
+individual_store_dispose (GObject *object)
+{
+  EmpathyIndividualStorePriv *priv = GET_PRIV (object);
+  GList *contacts, *l;
+
+  if (priv->dispose_has_run)
+    return;
+  priv->dispose_has_run = TRUE;
+
+  contacts = empathy_individual_manager_get_members (priv->manager);
+  for (l = contacts; l; l = l->next)
+    {
+      g_signal_handlers_disconnect_by_func (l->data,
+          G_CALLBACK (individual_store_contact_updated_cb), object);
+    }
+  g_list_free (contacts);
+
+  g_signal_handlers_disconnect_by_func (priv->manager,
+      G_CALLBACK (individual_store_member_renamed_cb), object);
+  g_signal_handlers_disconnect_by_func (priv->manager,
+      G_CALLBACK (individual_store_members_changed_cb), object);
+  g_signal_handlers_disconnect_by_func (priv->manager,
+      G_CALLBACK (individual_store_favourites_changed_cb), object);
+  g_signal_handlers_disconnect_by_func (priv->manager,
+      G_CALLBACK (individual_store_groups_changed_cb), object);
+  g_object_unref (priv->manager);
+
+  if (priv->inhibit_active)
+    {
+      g_source_remove (priv->inhibit_active);
+    }
+
+  if (priv->setup_idle_id != 0)
+    {
+      g_source_remove (priv->setup_idle_id);
+    }
+
+  g_hash_table_destroy (priv->status_icons);
+  G_OBJECT_CLASS (empathy_individual_store_parent_class)->dispose (object);
+}
+
+static void
+individual_store_get_property (GObject *object,
+    guint param_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (object);
+
+  switch (param_id)
+    {
+    case PROP_INDIVIDUAL_MANAGER:
+      g_value_set_object (value, priv->manager);
+      break;
+    case PROP_SHOW_OFFLINE:
+      g_value_set_boolean (value, priv->show_offline);
+      break;
+    case PROP_SHOW_AVATARS:
+      g_value_set_boolean (value, priv->show_avatars);
+      break;
+    case PROP_SHOW_PROTOCOLS:
+      g_value_set_boolean (value, priv->show_protocols);
+      break;
+    case PROP_SHOW_GROUPS:
+      g_value_set_boolean (value, priv->show_groups);
+      break;
+    case PROP_IS_COMPACT:
+      g_value_set_boolean (value, priv->is_compact);
+      break;
+    case PROP_SORT_CRITERIUM:
+      g_value_set_enum (value, priv->sort_criterium);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+individual_store_set_property (GObject *object,
+    guint param_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (object);
+
+  switch (param_id)
+    {
+    case PROP_INDIVIDUAL_MANAGER:
+      individual_store_set_individual_manager (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_object (value));
+      break;
+    case PROP_SHOW_OFFLINE:
+      empathy_individual_store_set_show_offline (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SHOW_AVATARS:
+      empathy_individual_store_set_show_avatars (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SHOW_PROTOCOLS:
+      empathy_individual_store_set_show_protocols (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SHOW_GROUPS:
+      empathy_individual_store_set_show_groups (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_IS_COMPACT:
+      empathy_individual_store_set_is_compact (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_boolean (value));
+      break;
+    case PROP_SORT_CRITERIUM:
+      empathy_individual_store_set_sort_criterium (EMPATHY_INDIVIDUAL_STORE
+          (object), g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+empathy_individual_store_class_init (EmpathyIndividualStoreClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = individual_store_dispose;
+  object_class->get_property = individual_store_get_property;
+  object_class->set_property = individual_store_set_property;
+
+  g_object_class_install_property (object_class,
+      PROP_INDIVIDUAL_MANAGER,
+      g_param_spec_object ("individual-manager",
+          "The individual manager",
+          "The individual manager",
+          EMPATHY_TYPE_INDIVIDUAL_MANAGER,
+          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_OFFLINE,
+      g_param_spec_boolean ("show-offline",
+          "Show Offline",
+          "Whether contact list should display "
+          "offline contacts", FALSE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_AVATARS,
+      g_param_spec_boolean ("show-avatars",
+          "Show Avatars",
+          "Whether contact list should display "
+          "avatars for contacts", TRUE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_PROTOCOLS,
+      g_param_spec_boolean ("show-protocols",
+          "Show Protocols",
+          "Whether contact list should display "
+          "protocols for contacts", FALSE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_SHOW_GROUPS,
+      g_param_spec_boolean ("show-groups",
+          "Show Groups",
+          "Whether contact list should display "
+          "contact groups", TRUE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_IS_COMPACT,
+      g_param_spec_boolean ("is-compact",
+          "Is Compact",
+          "Whether the contact list is in compact mode or not",
+          FALSE, G_PARAM_READWRITE));
+
+  g_object_class_install_property (object_class,
+      PROP_SORT_CRITERIUM,
+      g_param_spec_enum ("sort-criterium",
+          "Sort citerium",
+          "The sort criterium to use for sorting the contact list",
+          EMPATHY_TYPE_INDIVIDUAL_STORE_SORT,
+          EMPATHY_INDIVIDUAL_STORE_SORT_NAME, G_PARAM_READWRITE));
+
+  g_type_class_add_private (object_class,
+      sizeof (EmpathyIndividualStorePriv));
+}
+
+static gint
+get_position (const char **strv,
+    const char *str)
+{
+  int i;
+
+  for (i = 0; strv[i] != NULL; i++)
+    {
+      if (!tp_strdiff (strv[i], str))
+        return i;
+    }
+
+  return -1;
+}
+
+static gint
+compare_separator_and_groups (gboolean is_separator_a,
+    gboolean is_separator_b,
+    const gchar *name_a,
+    const gchar *name_b,
+    FolksIndividual *individual_a,
+    FolksIndividual *individual_b,
+    gboolean fake_group_a,
+    gboolean fake_group_b)
+{
+  /* these two lists are the sorted list of fake groups to include at the
+   * top and bottom of the roster */
+  const char *top_groups[] = {
+    EMPATHY_INDIVIDUAL_STORE_FAVORITE,
+    NULL
+  };
+
+  const char *bottom_groups[] = {
+    EMPATHY_INDIVIDUAL_STORE_UNGROUPED,
+    NULL
+  };
+
+  if (is_separator_a || is_separator_b)
+    {
+      /* We have at least one separator */
+      if (is_separator_a)
+        {
+          return -1;
+        }
+      else if (is_separator_b)
+        {
+          return 1;
+        }
+    }
+
+  /* One group and one contact */
+  if (!individual_a && individual_b)
+    {
+      return 1;
+    }
+  else if (individual_a && !individual_b)
+    {
+      return -1;
+    }
+  else if (!individual_a && !individual_b)
+    {
+      gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
+
+      a_in_top = fake_group_a && tp_strv_contains (top_groups, name_a);
+      b_in_top = fake_group_b && tp_strv_contains (top_groups, name_b);
+      a_in_bottom = fake_group_a && tp_strv_contains (bottom_groups, name_a);
+      b_in_bottom = fake_group_b && tp_strv_contains (bottom_groups, name_b);
+
+      if (a_in_top && b_in_top)
+        {
+          /* compare positions */
+          return CLAMP (get_position (top_groups, name_a) -
+              get_position (top_groups, name_b), -1, 1);
+        }
+      else if (a_in_bottom && b_in_bottom)
+        {
+          /* compare positions */
+          return CLAMP (get_position (bottom_groups, name_a) -
+              get_position (bottom_groups, name_b), -1, 1);
+        }
+      else if (a_in_top || b_in_bottom)
+        {
+          return -1;
+        }
+      else if (b_in_top || a_in_bottom)
+        {
+          return 1;
+        }
+      else
+        {
+          return g_utf8_collate (name_a, name_b);
+        }
+    }
+
+  /* Two contacts, ordering depends of the sorting policy */
+  return 0;
+}
+
+static gint
+individual_store_contact_sort (FolksIndividual *individual_a,
+    FolksIndividual *individual_b)
+{
+  gint ret_val;
+  EmpathyContact *contact_a = NULL, *contact_b = NULL;
+  TpAccount *account_a, *account_b;
+
+  g_return_val_if_fail (individual_a != NULL || individual_b != NULL, 0);
+
+  /* alias */
+  ret_val = g_utf8_collate (folks_individual_get_alias (individual_a),
+      folks_individual_get_alias (individual_b));
+
+  if (ret_val != 0)
+    goto out;
+
+  contact_a = empathy_contact_dup_from_folks_individual (individual_a);
+  contact_b = empathy_contact_dup_from_folks_individual (individual_b);
+  account_a = empathy_contact_get_account (contact_a);
+  account_b = empathy_contact_get_account (contact_b);
+
+  /* protocol */
+  ret_val = g_strcmp0 (tp_account_get_protocol (account_a),
+      tp_account_get_protocol (account_b));
+
+  if (ret_val != 0)
+    goto out;
+
+  /* account ID */
+  ret_val = g_strcmp0 (tp_proxy_get_object_path (account_a),
+      tp_proxy_get_object_path (account_b));
+
+  if (ret_val != 0)
+    goto out;
+
+  /* identifier */
+  ret_val = g_utf8_collate (folks_individual_get_id (individual_a),
+      folks_individual_get_id (individual_b));
+
+out:
+  tp_clear_object (&contact_a);
+  tp_clear_object (&contact_b);
+
+  return ret_val;
+}
+
+static gint
+individual_store_state_sort_func (GtkTreeModel *model,
+    GtkTreeIter *iter_a,
+    GtkTreeIter *iter_b,
+    gpointer user_data)
+{
+  gint ret_val;
+  FolksIndividual *individual_a, *individual_b;
+  gchar *name_a, *name_b;
+  gboolean is_separator_a, is_separator_b;
+  gboolean fake_group_a, fake_group_b;
+  FolksPresenceType folks_presence_type_a, folks_presence_type_b;
+  TpConnectionPresenceType tp_presence_a, tp_presence_b;
+
+  gtk_tree_model_get (model, iter_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+  gtk_tree_model_get (model, iter_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+  if (individual_a == NULL || individual_b == NULL)
+    {
+      ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+          name_a, name_b, individual_a, individual_b, fake_group_a,
+          fake_group_b);
+      goto free_and_out;
+    }
+
+  /* If we managed to get this far, we can start looking at
+   * the presences.
+   */
+  folks_presence_type_a = folks_individual_get_presence_type (individual_a);
+  folks_presence_type_b = folks_individual_get_presence_type (individual_b);
+  tp_presence_a = empathy_folks_presence_type_to_tp (folks_presence_type_a);
+  tp_presence_b = empathy_folks_presence_type_to_tp (folks_presence_type_b);
+
+  ret_val = -tp_connection_presence_type_cmp_availability (tp_presence_a,
+      tp_presence_b);
+
+  if (ret_val == 0)
+    {
+      /* Fallback: compare by name et al. */
+      ret_val = individual_store_contact_sort (individual_a, individual_b);
+    }
+
+free_and_out:
+  g_free (name_a);
+  g_free (name_b);
+  tp_clear_object (&individual_a);
+  tp_clear_object (&individual_b);
+
+  return ret_val;
+}
+
+static gint
+individual_store_name_sort_func (GtkTreeModel *model,
+    GtkTreeIter *iter_a,
+    GtkTreeIter *iter_b,
+    gpointer user_data)
+{
+  gchar *name_a, *name_b;
+  FolksIndividual *individual_a, *individual_b;
+  gboolean is_separator_a = FALSE, is_separator_b = FALSE;
+  gint ret_val;
+  gboolean fake_group_a, fake_group_b;
+
+  gtk_tree_model_get (model, iter_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_a,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_a, -1);
+  gtk_tree_model_get (model, iter_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator_b,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake_group_b, -1);
+
+  if (individual_a == NULL || individual_b == NULL)
+    ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
+        name_a, name_b, individual_a, individual_b, fake_group_a, fake_group_b);
+  else
+    ret_val = individual_store_contact_sort (individual_a, individual_b);
+
+  tp_clear_object (&individual_a);
+  tp_clear_object (&individual_b);
+
+  return ret_val;
+}
+
+static void
+individual_store_setup (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+  GType types[] = {
+    GDK_TYPE_PIXBUF,            /* Status pixbuf */
+    GDK_TYPE_PIXBUF,            /* Avatar pixbuf */
+    G_TYPE_BOOLEAN,             /* Avatar pixbuf visible */
+    G_TYPE_STRING,              /* Name */
+    G_TYPE_UINT,                /* Presence type */
+    G_TYPE_STRING,              /* Status string */
+    G_TYPE_BOOLEAN,             /* Compact view */
+    FOLKS_TYPE_INDIVIDUAL,      /* Individual type */
+    G_TYPE_BOOLEAN,             /* Is group */
+    G_TYPE_BOOLEAN,             /* Is active */
+    G_TYPE_BOOLEAN,             /* Is online */
+    G_TYPE_BOOLEAN,             /* Is separator */
+    G_TYPE_BOOLEAN,             /* Can make audio calls */
+    G_TYPE_BOOLEAN,             /* Can make video calls */
+    EMPATHY_TYPE_INDIVIDUAL_MANAGER_FLAGS,      /* Flags */
+    G_TYPE_BOOLEAN,             /* Is a fake group */
+  };
+
+  priv = GET_PRIV (self);
+
+  gtk_tree_store_set_column_types (GTK_TREE_STORE (self),
+      EMPATHY_INDIVIDUAL_STORE_COL_COUNT, types);
+
+  /* Set up sorting */
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME,
+      individual_store_name_sort_func, self, NULL);
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (self),
+      EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
+      individual_store_state_sort_func, self, NULL);
+
+  priv->sort_criterium = EMPATHY_INDIVIDUAL_STORE_SORT_NAME;
+  empathy_individual_store_set_sort_criterium (self, priv->sort_criterium);
+}
+
+static gboolean
+individual_store_inibit_active_cb (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  priv = GET_PRIV (self);
+
+  priv->show_active = TRUE;
+  priv->inhibit_active = 0;
+
+  return FALSE;
+}
+
+static void
+empathy_individual_store_init (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStorePriv);
+
+  self->priv = priv;
+  priv->show_avatars = TRUE;
+  priv->show_groups = TRUE;
+  priv->show_protocols = FALSE;
+  priv->inhibit_active =
+      g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
+      (GSourceFunc) individual_store_inibit_active_cb, self);
+  priv->status_icons =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+  individual_store_setup (self);
+}
+
+EmpathyIndividualStore *
+empathy_individual_store_new (EmpathyIndividualManager *manager)
+{
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager), NULL);
+
+  return g_object_new (EMPATHY_TYPE_INDIVIDUAL_STORE,
+      "individual-manager", manager, NULL);
+}
+
+EmpathyIndividualManager *
+empathy_individual_store_get_manager (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), FALSE);
+
+  priv = GET_PRIV (self);
+
+  return priv->manager;
+}
+
+gboolean
+empathy_individual_store_get_show_offline (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), FALSE);
+
+  priv = GET_PRIV (self);
+
+  return priv->show_offline;
+}
+
+void
+empathy_individual_store_set_show_offline (EmpathyIndividualStore *self,
+    gboolean show_offline)
+{
+  EmpathyIndividualStorePriv *priv;
+  GList *contacts, *l;
+  gboolean show_active;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  priv->show_offline = show_offline;
+  show_active = priv->show_active;
+
+  /* Disable temporarily. */
+  priv->show_active = FALSE;
+
+  contacts = empathy_individual_manager_get_members (priv->manager);
+  for (l = contacts; l; l = l->next)
+    {
+      individual_store_contact_update (self, l->data);
+    }
+  g_list_free (contacts);
+
+  /* Restore to original setting. */
+  priv->show_active = show_active;
+
+  g_object_notify (G_OBJECT (self), "show-offline");
+}
+
+gboolean
+empathy_individual_store_get_show_avatars (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  priv = GET_PRIV (self);
+
+  return priv->show_avatars;
+}
+
+static gboolean
+individual_store_update_list_mode_foreach (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+  gboolean show_avatar = FALSE;
+  FolksIndividual *individual;
+  GdkPixbuf *pixbuf_status;
+
+  priv = GET_PRIV (self);
+
+  if (priv->show_avatars && !priv->is_compact)
+    {
+      show_avatar = TRUE;
+    }
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  if (individual == NULL)
+    {
+      return FALSE;
+    }
+  /* get icon from hash_table */
+  pixbuf_status =
+      empathy_individual_store_get_individual_status_icon (self, individual);
+
+  gtk_tree_store_set (GTK_TREE_STORE (self), iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf_status,
+      EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
+      EMPATHY_INDIVIDUAL_STORE_COL_COMPACT, priv->is_compact, -1);
+
+  g_object_unref (individual);
+
+  return FALSE;
+}
+
+void
+empathy_individual_store_set_show_avatars (EmpathyIndividualStore *self,
+    gboolean show_avatars)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  priv->show_avatars = show_avatars;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "show-avatars");
+}
+
+gboolean
+empathy_individual_store_get_show_protocols (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  priv = GET_PRIV (self);
+
+  return priv->show_protocols;
+}
+
+void
+empathy_individual_store_set_show_protocols (EmpathyIndividualStore *self,
+    gboolean show_protocols)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  priv->show_protocols = show_protocols;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "show-protocols");
+}
+
+gboolean
+empathy_individual_store_get_show_groups (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  priv = GET_PRIV (self);
+
+  return priv->show_groups;
+}
+
+void
+empathy_individual_store_set_show_groups (EmpathyIndividualStore *self,
+    gboolean show_groups)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  if (priv->show_groups == show_groups)
+    {
+      return;
+    }
+
+  priv->show_groups = show_groups;
+
+  if (priv->setup_idle_id == 0)
+    {
+      /* Remove all contacts and add them back, not optimized but
+       * that's the easy way :)
+       *
+       * This is only done if there's not a pending setup idle
+       * callback, otherwise it will race and the contacts will get
+       * added twice */
+      GList *contacts;
+
+      gtk_tree_store_clear (GTK_TREE_STORE (self));
+      contacts = empathy_individual_manager_get_members (priv->manager);
+
+      individual_store_members_changed_cb (priv->manager,
+          "re-adding members: toggled group visibility",
+          contacts, NULL, 0, self);
+      g_list_free (contacts);
+    }
+
+  g_object_notify (G_OBJECT (self), "show-groups");
+}
+
+gboolean
+empathy_individual_store_get_is_compact (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), TRUE);
+
+  priv = GET_PRIV (self);
+
+  return priv->is_compact;
+}
+
+void
+empathy_individual_store_set_is_compact (EmpathyIndividualStore *self,
+    gboolean is_compact)
+{
+  EmpathyIndividualStorePriv *priv;
+  GtkTreeModel *model;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  priv->is_compact = is_compact;
+
+  model = GTK_TREE_MODEL (self);
+
+  gtk_tree_model_foreach (model,
+      (GtkTreeModelForeachFunc)
+      individual_store_update_list_mode_foreach, self);
+
+  g_object_notify (G_OBJECT (self), "is-compact");
+}
+
+EmpathyIndividualStoreSort
+empathy_individual_store_get_sort_criterium (EmpathyIndividualStore *self)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self), 0);
+
+  priv = GET_PRIV (self);
+
+  return priv->sort_criterium;
+}
+
+void
+empathy_individual_store_set_sort_criterium (EmpathyIndividualStore *self,
+    EmpathyIndividualStoreSort sort_criterium)
+{
+  EmpathyIndividualStorePriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (self));
+
+  priv = GET_PRIV (self);
+
+  priv->sort_criterium = sort_criterium;
+
+  switch (sort_criterium)
+    {
+    case EMPATHY_INDIVIDUAL_STORE_SORT_STATE:
+      gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+          EMPATHY_INDIVIDUAL_STORE_COL_STATUS, GTK_SORT_ASCENDING);
+      break;
+
+    case EMPATHY_INDIVIDUAL_STORE_SORT_NAME:
+      gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+          EMPATHY_INDIVIDUAL_STORE_COL_NAME, GTK_SORT_ASCENDING);
+      break;
+    }
+
+  g_object_notify (G_OBJECT (self), "sort-criterium");
+}
+
+gboolean
+empathy_individual_store_row_separator_func (GtkTreeModel *model,
+    GtkTreeIter *iter,
+    gpointer data)
+{
+  gboolean is_separator = FALSE;
+
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator, -1);
+
+  return is_separator;
+}
+
+gchar *
+empathy_individual_store_get_parent_group (GtkTreeModel *model,
+    GtkTreePath *path,
+    gboolean *path_is_group,
+    gboolean *is_fake_group)
+{
+  GtkTreeIter parent_iter, iter;
+  gchar *name = NULL;
+  gboolean is_group;
+  gboolean fake;
+
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
+
+  if (path_is_group)
+    {
+      *path_is_group = FALSE;
+    }
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    {
+      return NULL;
+    }
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+  if (!is_group)
+    {
+      g_free (name);
+      name = NULL;
+
+      if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter))
+        {
+          return NULL;
+        }
+
+      iter = parent_iter;
+
+      gtk_tree_model_get (model, &iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+          EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
+      if (!is_group)
+        {
+          g_free (name);
+          return NULL;
+        }
+    }
+
+  if (path_is_group)
+    {
+      *path_is_group = TRUE;
+    }
+
+  if (is_fake_group != NULL)
+    *is_fake_group = fake;
+
+  return name;
+}
+
+static GdkPixbuf *
+individual_store_get_individual_status_icon_with_icon_name (
+    EmpathyIndividualStore *self,
+    FolksIndividual *individual,
+    const gchar *status_icon_name)
+{
+  GdkPixbuf *pixbuf_status = NULL;
+  EmpathyIndividualStorePriv *priv;
+  const gchar *protocol_name = NULL;
+  gchar *icon_name = NULL;
+  GList *personas, *l;
+  guint contact_count;
+  EmpathyContact *contact = NULL;
+  gboolean show_protocols_here;
+
+  priv = GET_PRIV (self);
+
+  personas = folks_individual_get_personas (individual);
+  for (l = personas, contact_count = 0; l; l = l->next)
+    {
+      if (TPF_IS_PERSONA (l->data))
+        contact_count++;
+
+      if (contact_count > 1)
+        break;
+    }
+
+  show_protocols_here = (priv->show_protocols && (contact_count == 1));
+  if (show_protocols_here)
+    {
+      contact = empathy_contact_dup_from_folks_individual (individual);
+      protocol_name = empathy_protocol_name_for_contact (contact);
+      icon_name = g_strdup_printf ("%s-%s", status_icon_name, protocol_name);
+    }
+  else
+    {
+      icon_name = g_strdup_printf ("%s", status_icon_name);
+    }
+  if (pixbuf_status == NULL)
+    {
+      pixbuf_status =
+          empathy_pixbuf_contact_status_icon_with_icon_name (contact,
+          status_icon_name, priv->show_protocols);
+      if (pixbuf_status != NULL)
+        {
+          g_hash_table_insert (priv->status_icons,
+              g_strdup (icon_name), pixbuf_status);
+        }
+    }
+
+  g_free (icon_name);
+  tp_clear_object (&contact);
+
+  return pixbuf_status;
+}
+
+GdkPixbuf *
+empathy_individual_store_get_individual_status_icon (
+    EmpathyIndividualStore *self,
+    FolksIndividual *individual)
+{
+  GdkPixbuf *pixbuf_status = NULL;
+  const gchar *status_icon_name = NULL;
+
+  status_icon_name = empathy_icon_name_for_individual (individual);
+  if (status_icon_name == NULL)
+    return NULL;
+
+  pixbuf_status =
+      individual_store_get_individual_status_icon_with_icon_name (self,
+      individual, status_icon_name);
+
+  return pixbuf_status;
+}
diff --git a/libempathy-gtk/empathy-individual-store.h b/libempathy-gtk/empathy-individual-store.h
new file mode 100644
index 0000000..9c85582
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-store.h
@@ -0,0 +1,148 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ *          Martyn Russell <martyn@imendio.com>
+ *          Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_STORE_H__
+#define __EMPATHY_INDIVIDUAL_STORE_H__
+
+#include <gtk/gtk.h>
+
+#include <libempathy/empathy-individual-manager.h>
+
+G_BEGIN_DECLS
+#define EMPATHY_TYPE_INDIVIDUAL_STORE         (empathy_individual_store_get_type ())
+#define EMPATHY_INDIVIDUAL_STORE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStore))
+#define EMPATHY_INDIVIDUAL_STORE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStoreClass))
+#define EMPATHY_IS_INDIVIDUAL_STORE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_INDIVIDUAL_STORE))
+#define EMPATHY_IS_INDIVIDUAL_STORE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_INDIVIDUAL_STORE))
+#define EMPATHY_INDIVIDUAL_STORE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_INDIVIDUAL_STORE, EmpathyIndividualStoreClass))
+typedef struct _EmpathyIndividualStore EmpathyIndividualStore;
+typedef struct _EmpathyIndividualStoreClass EmpathyIndividualStoreClass;
+
+typedef enum
+{
+  EMPATHY_INDIVIDUAL_STORE_SORT_STATE,
+  EMPATHY_INDIVIDUAL_STORE_SORT_NAME
+} EmpathyIndividualStoreSort;
+
+typedef enum
+{
+  EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS,
+  EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR,
+  EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE,
+  EMPATHY_INDIVIDUAL_STORE_COL_NAME,
+  EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE,
+  EMPATHY_INDIVIDUAL_STORE_COL_STATUS,
+  EMPATHY_INDIVIDUAL_STORE_COL_COMPACT,
+  EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
+  EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP,
+  EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE,
+  EMPATHY_INDIVIDUAL_STORE_COL_IS_ONLINE,
+  EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR,
+  EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL,
+  EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL,
+  EMPATHY_INDIVIDUAL_STORE_COL_FLAGS,
+  EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP,
+  EMPATHY_INDIVIDUAL_STORE_COL_COUNT,
+} EmpathyIndividualStoreCol;
+
+#define EMPATHY_INDIVIDUAL_STORE_UNGROUPED _("Ungrouped")
+#define EMPATHY_INDIVIDUAL_STORE_FAVORITE  _("Favorite People")
+#define EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY _("People Nearby")
+
+struct _EmpathyIndividualStore
+{
+  GtkTreeStore parent;
+  gpointer priv;
+};
+
+struct _EmpathyIndividualStoreClass
+{
+  GtkTreeStoreClass parent_class;
+};
+
+GType
+empathy_individual_store_get_type (void) G_GNUC_CONST;
+
+EmpathyIndividualStore *empathy_individual_store_new (
+    EmpathyIndividualManager *manager);
+
+EmpathyIndividualManager *empathy_individual_store_get_manager (
+    EmpathyIndividualStore *store);
+
+gboolean empathy_individual_store_get_show_offline (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_show_offline (
+    EmpathyIndividualStore *store,
+    gboolean show_offline);
+
+gboolean empathy_individual_store_get_show_avatars (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_show_avatars (EmpathyIndividualStore *store,
+    gboolean show_avatars);
+
+gboolean empathy_individual_store_get_show_groups (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_show_groups (EmpathyIndividualStore *store,
+    gboolean show_groups);
+
+gboolean empathy_individual_store_get_is_compact (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_is_compact (EmpathyIndividualStore *store,
+    gboolean is_compact);
+
+gboolean empathy_individual_store_get_show_protocols (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_show_protocols (
+    EmpathyIndividualStore *store,
+    gboolean show_protocols);
+
+EmpathyIndividualStoreSort empathy_individual_store_get_sort_criterium (
+    EmpathyIndividualStore *store);
+
+void empathy_individual_store_set_sort_criterium (
+    EmpathyIndividualStore *store,
+    EmpathyIndividualStoreSort sort_criterium);
+
+gboolean empathy_individual_store_row_separator_func (GtkTreeModel *model,
+    GtkTreeIter *iter,
+    gpointer data);
+
+gchar *empathy_individual_store_get_parent_group (GtkTreeModel *model,
+    GtkTreePath *path,
+    gboolean *path_is_group,
+    gboolean *is_fake_group);
+
+GdkPixbuf *empathy_individual_store_get_individual_status_icon (
+    EmpathyIndividualStore *store,
+    FolksIndividual *individual);
+
+G_END_DECLS
+#endif /* __EMPATHY_INDIVIDUAL_STORE_H__ */
diff --git a/libempathy-gtk/empathy-individual-view.c b/libempathy-gtk/empathy-individual-view.c
new file mode 100644
index 0000000..ef4f938
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-view.c
@@ -0,0 +1,2146 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ *          Martyn Russell <martyn@imendio.com>
+ *          Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+#include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/util.h>
+
+#include <libempathy/empathy-call-factory.h>
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-contact-groups.h>
+#include <libempathy/empathy-dispatcher.h>
+#include <libempathy/empathy-utils.h>
+
+#include "empathy-individual-view.h"
+#include "empathy-individual-menu.h"
+#include "empathy-individual-store.h"
+#include "empathy-images.h"
+#include "empathy-cell-renderer-expander.h"
+#include "empathy-cell-renderer-text.h"
+#include "empathy-cell-renderer-activatable.h"
+#include "empathy-ui-utils.h"
+#include "empathy-gtk-enum-types.h"
+#include "empathy-gtk-marshal.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
+#include <libempathy/empathy-debug.h>
+
+/* Active users are those which have recently changed state
+ * (e.g. online, offline or from normal to a busy state).
+ */
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
+typedef struct
+{
+  EmpathyIndividualStore *store;
+  GtkTreeRowReference *drag_row;
+  EmpathyIndividualViewFeatureFlags view_features;
+  EmpathyContactFeatureFlags individual_features;
+  GtkWidget *tooltip_widget;
+  GtkTargetList *file_targets;
+
+  GtkTreeModelFilter *filter;
+  GtkWidget *search_widget;
+} EmpathyIndividualViewPriv;
+
+typedef struct
+{
+  EmpathyIndividualView *view;
+  GtkTreePath *path;
+  guint timeout_id;
+} DragMotionData;
+
+typedef struct
+{
+  EmpathyIndividualView *view;
+  FolksIndividual *individual;
+  gboolean remove;
+} ShowActiveData;
+
+enum
+{
+  PROP_0,
+  PROP_STORE,
+  PROP_VIEW_FEATURES,
+  PROP_INDIVIDUAL_FEATURES,
+};
+
+/* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
+ * specific EmpathyContacts (between/in/out of Individuals) */
+enum DndDragType
+{
+  DND_DRAG_TYPE_INDIVIDUAL_ID,
+  DND_DRAG_TYPE_URI_LIST,
+  DND_DRAG_TYPE_STRING,
+};
+
+static const GtkTargetEntry drag_types_dest[] = {
+  {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
+  {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
+  {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
+  {"text/plain", 0, DND_DRAG_TYPE_STRING},
+  {"STRING", 0, DND_DRAG_TYPE_STRING},
+};
+
+static const GtkTargetEntry drag_types_dest_file[] = {
+  {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
+  {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
+};
+
+static const GtkTargetEntry drag_types_source[] = {
+  {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
+};
+
+static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
+static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
+
+enum
+{
+  DRAG_CONTACT_RECEIVED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
+    GTK_TYPE_TREE_VIEW);
+
+static gboolean
+individual_view_is_visible_individual (EmpathyIndividualView *self,
+    FolksIndividual *individual)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (self);
+  EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
+  const gchar *str;
+  GList *personas, *l;
+
+  g_assert (live != NULL);
+
+  /* check alias name */
+  str = folks_individual_get_alias (individual);
+  if (empathy_live_search_match (live, str))
+    return TRUE;
+
+  /* check contact id, remove the @server.com part */
+  personas = folks_individual_get_personas (individual);
+  for (l = personas; l; l = l->next)
+    {
+      const gchar *p;
+      gchar *dup_str = NULL;
+      gboolean visible;
+
+      str = folks_persona_get_uid (l->data);
+      p = strstr (str, "@");
+      if (p != NULL)
+        str = dup_str = g_strndup (str, p - str);
+
+      visible = empathy_live_search_match (live, str);
+      g_free (dup_str);
+      if (visible)
+        return TRUE;
+    }
+
+  /* FIXME: Add more rules here, we could check phone numbers in
+   * contact's vCard for example. */
+
+  return FALSE;
+}
+
+static gboolean
+individual_view_filter_visible_func (GtkTreeModel *model,
+    GtkTreeIter *iter,
+    gpointer user_data)
+{
+  EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
+  EmpathyIndividualViewPriv *priv = GET_PRIV (self);
+  FolksIndividual *individual = NULL;
+  gboolean is_group, is_separator, valid;
+  GtkTreeIter child_iter;
+  gboolean visible;
+
+  if (priv->search_widget == NULL ||
+      !gtk_widget_get_visible (priv->search_widget))
+    return TRUE;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
+      -1);
+
+  if (individual != NULL)
+    {
+      visible = individual_view_is_visible_individual (self, individual);
+      g_object_unref (individual);
+      return visible;
+    }
+
+  if (is_separator)
+    return TRUE;
+
+  /* Not a contact, not a separator, must be a group */
+  g_return_val_if_fail (is_group, FALSE);
+
+  /* only show groups which are not empty */
+  for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
+       valid; valid = gtk_tree_model_iter_next (model, &child_iter))
+    {
+      gtk_tree_model_get (model, &child_iter,
+        EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
+        -1);
+
+      if (individual == NULL)
+        continue;
+
+      visible = individual_view_is_visible_individual (self, individual);
+      g_object_unref (individual);
+
+      /* show group if it has at least one visible contact in it */
+      if (visible)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+individual_view_tooltip_destroy_cb (GtkWidget *widget,
+    EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+
+  if (priv->tooltip_widget != NULL)
+    {
+      DEBUG ("Tooltip destroyed");
+      tp_clear_object (&priv->tooltip_widget);
+    }
+}
+
+static gboolean
+individual_view_query_tooltip_cb (EmpathyIndividualView *view,
+    gint x,
+    gint y,
+    gboolean keyboard_mode,
+    GtkTooltip *tooltip,
+    gpointer user_data)
+{
+  EmpathyIndividualViewPriv *priv;
+  FolksIndividual *individual;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  GtkTreePath *path;
+  static gint running = 0;
+  gboolean ret = FALSE;
+  EmpathyContact *contact;
+
+  priv = GET_PRIV (view);
+
+  /* Avoid an infinite loop. See GNOME bug #574377 */
+  if (running > 0)
+    return FALSE;
+
+  running++;
+
+  /* Don't show the tooltip if there's already a popup menu */
+  if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
+    goto OUT;
+
+  if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
+          keyboard_mode, &model, &path, &iter))
+    goto OUT;
+
+  gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
+  gtk_tree_path_free (path);
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
+      -1);
+  if (individual == NULL)
+    goto OUT;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  g_object_unref (individual);
+
+  if (contact == NULL)
+    goto OUT;
+
+  if (priv->tooltip_widget == NULL)
+    {
+      priv->tooltip_widget = empathy_contact_widget_new (contact,
+          EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
+          EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
+      gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
+      g_object_ref (priv->tooltip_widget);
+      g_signal_connect (priv->tooltip_widget, "destroy",
+          G_CALLBACK (individual_view_tooltip_destroy_cb), view);
+      gtk_widget_show (priv->tooltip_widget);
+    }
+  else
+    empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
+
+  gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
+  ret = TRUE;
+
+  g_object_unref (contact);
+OUT:
+  running--;
+
+  return ret;
+}
+
+static void
+individual_view_handle_drag (EmpathyIndividualView *self,
+    FolksIndividual *individual,
+    const gchar *old_group,
+    const gchar *new_group,
+    GdkDragAction action)
+{
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
+  g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+
+  DEBUG ("individual %s dragged from '%s' to '%s'",
+      folks_individual_get_id (individual), old_group, new_group);
+
+  if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
+    {
+      /* Mark contact as favourite */
+      folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
+      return;
+    }
+
+  if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
+    {
+      /* Remove contact as favourite */
+      folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
+
+      /* Don't try to remove it */
+      old_group = NULL;
+    }
+
+  if (new_group != NULL)
+    folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
+
+  if (old_group != NULL && action == GDK_ACTION_MOVE)
+    folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
+}
+
+static gboolean
+group_can_be_modified (const gchar *name,
+    gboolean is_fake_group,
+    gboolean adding)
+{
+  /* Real groups can always be modified */
+  if (!is_fake_group)
+    return TRUE;
+
+  /* The favorite fake group can be modified so users can
+   * add/remove favorites using DnD */
+  if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
+    return TRUE;
+
+  /* We can remove contacts from the 'ungrouped' fake group */
+  if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
+    return TRUE;
+
+  return FALSE;
+}
+
+static gboolean
+individual_view_contact_drag_received (GtkWidget *self,
+    GdkDragContext *context,
+    GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkSelectionData *selection)
+{
+  EmpathyIndividualViewPriv *priv;
+  EmpathyIndividualManager *manager;
+  FolksIndividual *individual;
+  GtkTreePath *source_path;
+  const gchar *sel_data;
+  gchar *new_group = NULL;
+  gchar *old_group = NULL;
+  gboolean new_group_is_fake, old_group_is_fake = TRUE;
+
+  priv = GET_PRIV (self);
+
+  sel_data = (const gchar *) gtk_selection_data_get_data (selection);
+  new_group = empathy_individual_store_get_parent_group (model, path,
+      NULL, &new_group_is_fake);
+
+  if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
+    return FALSE;
+
+  /* Get source group information. */
+  if (priv->drag_row)
+    {
+      source_path = gtk_tree_row_reference_get_path (priv->drag_row);
+      if (source_path)
+        {
+          old_group =
+              empathy_individual_store_get_parent_group (model, source_path,
+              NULL, &old_group_is_fake);
+          gtk_tree_path_free (source_path);
+        }
+    }
+
+  if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
+    return FALSE;
+
+  if (!tp_strdiff (old_group, new_group))
+    {
+      g_free (new_group);
+      g_free (old_group);
+      return FALSE;
+    }
+
+  /* XXX: for contacts, we used to ensure the account, create the contact
+   * factory, and then wait on the contacts. But they should already be
+   * created by this point */
+
+  manager = empathy_individual_manager_dup_singleton ();
+  individual = empathy_individual_manager_lookup_member (manager, sel_data);
+
+  if (individual == NULL)
+    {
+      DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
+
+      g_object_unref (manager);
+
+      return FALSE;
+    }
+
+  /* FIXME: We should probably wait for the cb before calling
+   * gtk_drag_finish */
+
+  individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
+      old_group, new_group, gdk_drag_context_get_selected_action (context));
+
+  g_object_unref (G_OBJECT (manager));
+  g_free (old_group);
+  g_free (new_group);
+
+  return TRUE;
+}
+
+static gboolean
+individual_view_file_drag_received (GtkWidget *view,
+    GdkDragContext *context,
+    GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkSelectionData *selection)
+{
+  GtkTreeIter iter;
+  const gchar *sel_data;
+  FolksIndividual *individual;
+  EmpathyContact *contact;
+
+  sel_data = (const gchar *) gtk_selection_data_get_data (selection);
+
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+  if (individual == NULL)
+    return FALSE;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  empathy_send_file_from_uri_list (contact, sel_data);
+
+  g_object_unref (individual);
+  tp_clear_object (&contact);
+
+  return TRUE;
+}
+
+static void
+individual_view_drag_data_received (GtkWidget *view,
+    GdkDragContext *context,
+    gint x,
+    gint y,
+    GtkSelectionData *selection,
+    guint info,
+    guint time_)
+{
+  GtkTreeModel *model;
+  gboolean is_row;
+  GtkTreeViewDropPosition position;
+  GtkTreePath *path;
+  gboolean success = TRUE;
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+  /* Get destination group information. */
+  is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
+      x, y, &path, &position);
+  if (!is_row)
+    {
+      success = FALSE;
+    }
+  else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
+      || info == DND_DRAG_TYPE_STRING)
+    {
+      success = individual_view_contact_drag_received (view,
+          context, model, path, selection);
+    }
+  else if (info == DND_DRAG_TYPE_URI_LIST)
+    {
+      success = individual_view_file_drag_received (view,
+          context, model, path, selection);
+    }
+
+  gtk_tree_path_free (path);
+  gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
+}
+
+static gboolean
+individual_view_drag_motion_cb (DragMotionData *data)
+{
+  gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
+
+  data->timeout_id = 0;
+
+  return FALSE;
+}
+
+static gboolean
+individual_view_drag_motion (GtkWidget *widget,
+    GdkDragContext *context,
+    gint x,
+    gint y,
+    guint time_)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreeModel *model;
+  GdkAtom target;
+  GtkTreeIter iter;
+  static DragMotionData *dm = NULL;
+  GtkTreePath *path;
+  gboolean is_row;
+  gboolean is_different = FALSE;
+  gboolean cleanup = TRUE;
+  gboolean retval = TRUE;
+
+  priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+
+  is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
+      x, y, &path, NULL, NULL, NULL);
+
+  cleanup &= (dm == NULL);
+
+  if (is_row)
+    {
+      cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
+      is_different = ((dm == NULL) || ((dm != NULL)
+              && gtk_tree_path_compare (dm->path, path) != 0));
+    }
+  else
+    cleanup &= FALSE;
+
+  if (path == NULL)
+    {
+      /* Coordinates don't point to an actual row, so make sure the pointer
+         and highlighting don't indicate that a drag is possible.
+       */
+      gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
+      gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
+      return FALSE;
+    }
+  target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
+  gtk_tree_model_get_iter (model, &iter, path);
+
+  if (target == GDK_NONE)
+    {
+      /* If target == GDK_NONE, then we don't have a target that can be
+         dropped on a contact.  This means a contact drag.  If we're
+         pointing to a group, highlight it.  Otherwise, if the contact
+         we're pointing to is in a group, highlight that.  Otherwise,
+         set the drag position to before the first row for a drag into
+         the "non-group" at the top.
+       */
+      GtkTreeIter group_iter;
+      gboolean is_group;
+      GtkTreePath *group_path;
+      gtk_tree_model_get (model, &iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
+      if (is_group)
+        {
+          group_iter = iter;
+        }
+      else
+        {
+          if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
+            gtk_tree_model_get (model, &group_iter,
+                EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
+        }
+      if (is_group)
+        {
+          gdk_drag_status (context, GDK_ACTION_MOVE, time_);
+          group_path = gtk_tree_model_get_path (model, &group_iter);
+          gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
+              group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
+          gtk_tree_path_free (group_path);
+        }
+      else
+        {
+          group_path = gtk_tree_path_new_first ();
+          gdk_drag_status (context, GDK_ACTION_MOVE, time_);
+          gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
+              group_path, GTK_TREE_VIEW_DROP_BEFORE);
+        }
+    }
+  else
+    {
+      /* This is a file drag, and it can only be dropped on contacts,
+         not groups.
+       */
+      FolksIndividual *individual;
+      EmpathyCapabilities caps = EMPATHY_CAPABILITIES_NONE;
+
+      gtk_tree_model_get (model, &iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+      if (individual != NULL)
+        {
+          EmpathyContact *contact = NULL;
+
+          contact = empathy_contact_dup_from_folks_individual (individual);
+          caps = empathy_contact_get_capabilities (contact);
+
+          tp_clear_object (&contact);
+        }
+
+      if (individual != NULL &&
+          folks_individual_is_online (individual) &&
+          (caps & EMPATHY_CAPABILITIES_FT))
+        {
+          gdk_drag_status (context, GDK_ACTION_COPY, time_);
+          gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
+              path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
+        }
+      else
+        {
+          gdk_drag_status (context, 0, time_);
+          gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
+          retval = FALSE;
+        }
+
+      if (individual != NULL)
+        g_object_unref (individual);
+    }
+
+  if (!is_different && !cleanup)
+    return retval;
+
+  if (dm)
+    {
+      gtk_tree_path_free (dm->path);
+      if (dm->timeout_id)
+        {
+          g_source_remove (dm->timeout_id);
+        }
+
+      g_free (dm);
+
+      dm = NULL;
+    }
+
+  if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
+    {
+      dm = g_new0 (DragMotionData, 1);
+
+      dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
+      dm->path = gtk_tree_path_copy (path);
+
+      dm->timeout_id = g_timeout_add_seconds (1,
+          (GSourceFunc) individual_view_drag_motion_cb, dm);
+    }
+
+  return retval;
+}
+
+static void
+individual_view_drag_begin (GtkWidget *widget,
+    GdkDragContext *context)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreeSelection *selection;
+  GtkTreeModel *model;
+  GtkTreePath *path;
+  GtkTreeIter iter;
+
+  priv = GET_PRIV (widget);
+
+  GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
+      context);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    return;
+
+  path = gtk_tree_model_get_path (model, &iter);
+  priv->drag_row = gtk_tree_row_reference_new (model, path);
+  gtk_tree_path_free (path);
+}
+
+static void
+individual_view_drag_data_get (GtkWidget *widget,
+    GdkDragContext *context,
+    GtkSelectionData *selection,
+    guint info,
+    guint time_)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreePath *src_path;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  FolksIndividual *individual;
+  const gchar *individual_id;
+
+  priv = GET_PRIV (widget);
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+  if (priv->drag_row == NULL)
+    return;
+
+  src_path = gtk_tree_row_reference_get_path (priv->drag_row);
+  if (src_path == NULL)
+    return;
+
+  if (!gtk_tree_model_get_iter (model, &iter, src_path))
+    {
+      gtk_tree_path_free (src_path);
+      return;
+    }
+
+  gtk_tree_path_free (src_path);
+
+  individual =
+      empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
+  if (individual == NULL)
+    return;
+
+  individual_id = folks_individual_get_id (individual);
+
+  switch (info)
+    {
+    case DND_DRAG_TYPE_INDIVIDUAL_ID:
+      gtk_selection_data_set (selection, drag_atoms_source[info], 8,
+          (guchar *) individual_id, strlen (individual_id) + 1);
+      break;
+    }
+
+  g_object_unref (individual);
+}
+
+static void
+individual_view_drag_end (GtkWidget *widget,
+    GdkDragContext *context)
+{
+  EmpathyIndividualViewPriv *priv;
+
+  priv = GET_PRIV (widget);
+
+  GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
+      context);
+
+  if (priv->drag_row)
+    {
+      gtk_tree_row_reference_free (priv->drag_row);
+      priv->drag_row = NULL;
+    }
+}
+
+static gboolean
+individual_view_drag_drop (GtkWidget *widget,
+    GdkDragContext *drag_context,
+    gint x,
+    gint y,
+    guint time_)
+{
+  return FALSE;
+}
+
+typedef struct
+{
+  EmpathyIndividualView *view;
+  guint button;
+  guint32 time;
+} MenuPopupData;
+
+static gboolean
+individual_view_popup_menu_idle_cb (gpointer user_data)
+{
+  MenuPopupData *data = user_data;
+  GtkWidget *menu;
+
+  menu = empathy_individual_view_get_individual_menu (data->view);
+  if (menu == NULL)
+    menu = empathy_individual_view_get_group_menu (data->view);
+
+  if (menu != NULL)
+    {
+      g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
+      gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
+          NULL);
+      gtk_widget_show (menu);
+      gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
+          data->time);
+      g_object_ref_sink (menu);
+      g_object_unref (menu);
+    }
+
+  g_slice_free (MenuPopupData, data);
+
+  return FALSE;
+}
+
+static gboolean
+individual_view_button_press_event_cb (EmpathyIndividualView *view,
+    GdkEventButton *event,
+    gpointer user_data)
+{
+  if (event->button == 3)
+    {
+      MenuPopupData *data;
+
+      data = g_slice_new (MenuPopupData);
+      data->view = view;
+      data->button = event->button;
+      data->time = event->time;
+      g_idle_add (individual_view_popup_menu_idle_cb, data);
+    }
+
+  return FALSE;
+}
+
+static gboolean
+individual_view_key_press_event_cb (EmpathyIndividualView *view,
+    GdkEventKey *event,
+    gpointer user_data)
+{
+  if (event->keyval == GDK_Menu)
+    {
+      MenuPopupData *data;
+
+      data = g_slice_new (MenuPopupData);
+      data->view = view;
+      data->button = 0;
+      data->time = event->time;
+      g_idle_add (individual_view_popup_menu_idle_cb, data);
+    }
+
+  return FALSE;
+}
+
+static void
+individual_view_row_activated (GtkTreeView *view,
+    GtkTreePath *path,
+    GtkTreeViewColumn *column)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  FolksIndividual *individual;
+  EmpathyContact *contact = NULL;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+
+  if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
+    return;
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  if (individual == NULL)
+    return;
+
+  contact = empathy_contact_dup_from_folks_individual (individual);
+  if (contact != NULL)
+    {
+      DEBUG ("Starting a chat");
+
+      empathy_dispatcher_chat_with_contact (contact,
+          gtk_get_current_event_time (), NULL, NULL);
+    }
+
+  g_object_unref (individual);
+  tp_clear_object (&contact);
+}
+
+static void
+individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
+    const gchar *path_string,
+    EmpathyIndividualView *view)
+{
+  GtkWidget *menu;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  FolksIndividual *individual;
+  GdkEventButton *event;
+  GtkMenuShell *shell;
+  GtkWidget *item;
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+  if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
+    return;
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+  if (individual == NULL)
+    return;
+
+  event = (GdkEventButton *) gtk_get_current_event ();
+
+  menu = gtk_menu_new ();
+  shell = GTK_MENU_SHELL (menu);
+
+  /* audio */
+  item = empathy_individual_audio_call_menu_item_new (individual);
+  gtk_menu_shell_append (shell, item);
+  gtk_widget_show (item);
+
+  /* video */
+  item = empathy_individual_video_call_menu_item_new (individual);
+  gtk_menu_shell_append (shell, item);
+  gtk_widget_show (item);
+
+  g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
+  gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
+  gtk_widget_show (menu);
+  gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
+      event->button, event->time);
+  g_object_ref_sink (menu);
+  g_object_unref (menu);
+
+  g_object_unref (individual);
+}
+
+static void
+individual_view_cell_set_background (EmpathyIndividualView *view,
+    GtkCellRenderer *cell,
+    gboolean is_group,
+    gboolean is_active)
+{
+  GdkColor color;
+  GtkStyle *style;
+
+  style = gtk_widget_get_style (GTK_WIDGET (view));
+
+  if (!is_group && is_active)
+    {
+      color = style->bg[GTK_STATE_SELECTED];
+
+      /* Here we take the current theme colour and add it to
+       * the colour for white and average the two. This
+       * gives a colour which is inline with the theme but
+       * slightly whiter.
+       */
+      color.red = (color.red + (style->white).red) / 2;
+      color.green = (color.green + (style->white).green) / 2;
+      color.blue = (color.blue + (style->white).blue) / 2;
+
+      g_object_set (cell, "cell-background-gdk", &color, NULL);
+    }
+  else
+    g_object_set (cell, "cell-background-gdk", NULL, NULL);
+}
+
+static void
+individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  GdkPixbuf *pixbuf;
+  gboolean is_group;
+  gboolean is_active;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
+      EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
+
+  g_object_set (cell,
+      "visible", !is_group,
+      "pixbuf", pixbuf,
+      NULL);
+
+  tp_clear_object (&pixbuf);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  GdkPixbuf *pixbuf = NULL;
+  gboolean is_group;
+  gchar *name;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+  if (!is_group)
+    goto out;
+
+  if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
+    {
+      pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
+          GTK_ICON_SIZE_MENU);
+    }
+  else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
+    {
+      pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
+          GTK_ICON_SIZE_MENU);
+    }
+
+out:
+  g_object_set (cell,
+      "visible", pixbuf != NULL,
+      "pixbuf", pixbuf,
+      NULL);
+
+  tp_clear_object (&pixbuf);
+
+  g_free (name);
+}
+
+static void
+individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  gboolean is_group;
+  gboolean is_active;
+  gboolean can_audio, can_video;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
+      EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
+      EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
+
+  g_object_set (cell,
+      "visible", !is_group && (can_audio || can_video),
+      "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
+      NULL);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  GdkPixbuf *pixbuf;
+  gboolean show_avatar;
+  gboolean is_group;
+  gboolean is_active;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
+      EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+  g_object_set (cell,
+      "visible", !is_group && show_avatar,
+      "pixbuf", pixbuf,
+      NULL);
+
+  tp_clear_object (&pixbuf);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  gboolean is_group;
+  gboolean is_active;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
+    GtkCellRenderer *cell,
+    GtkTreeModel *model,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  gboolean is_group;
+  gboolean is_active;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
+
+  if (gtk_tree_model_iter_has_child (model, iter))
+    {
+      GtkTreePath *path;
+      gboolean row_expanded;
+
+      path = gtk_tree_model_get_path (model, iter);
+      row_expanded =
+          gtk_tree_view_row_expanded (GTK_TREE_VIEW
+          (gtk_tree_view_column_get_tree_view (column)), path);
+      gtk_tree_path_free (path);
+
+      g_object_set (cell,
+          "visible", TRUE,
+          "expander-style",
+          row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
+          NULL);
+    }
+  else
+    g_object_set (cell, "visible", FALSE, NULL);
+
+  individual_view_cell_set_background (view, cell, is_group, is_active);
+}
+
+static void
+individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
+    GtkTreeIter *iter,
+    GtkTreePath *path,
+    gpointer user_data)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  GtkTreeModel *model;
+  gchar *name;
+  gboolean expanded;
+
+  if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
+    return;
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
+
+  expanded = GPOINTER_TO_INT (user_data);
+  empathy_contact_group_set_expanded (name, expanded);
+
+  g_free (name);
+}
+
+static gboolean
+individual_view_start_search_cb (EmpathyIndividualView *view,
+    gpointer data)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+
+  if (priv->search_widget == NULL)
+    return FALSE;
+
+  if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
+    gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
+  else
+    gtk_widget_show (GTK_WIDGET (priv->search_widget));
+
+  return TRUE;
+}
+
+static void
+individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
+    GParamSpec *pspec,
+    EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  GtkTreePath *path;
+  GtkTreeViewColumn *focus_column;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gboolean set_cursor = FALSE;
+
+  gtk_tree_model_filter_refilter (priv->filter);
+
+  /* Set cursor on the first contact. If it is already set on a group,
+   * set it on its first child contact. Note that first child of a group
+   * is its separator, that's why we actually set to the 2nd
+   */
+
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+  gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+
+  if (path == NULL)
+    {
+      path = gtk_tree_path_new_from_string ("0:1");
+      set_cursor = TRUE;
+    }
+  else if (gtk_tree_path_get_depth (path) < 2)
+    {
+      gboolean is_group;
+
+      gtk_tree_model_get_iter (model, &iter, path);
+      gtk_tree_model_get (model, &iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+          -1);
+
+      if (is_group)
+        {
+          gtk_tree_path_down (path);
+          gtk_tree_path_next (path);
+          set_cursor = TRUE;
+        }
+    }
+
+  if (set_cursor)
+    {
+      /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
+       * valid. */
+      if (gtk_tree_model_get_iter (model, &iter, path))
+        {
+          gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
+              FALSE);
+        }
+    }
+
+  gtk_tree_path_free (path);
+}
+
+static void
+individual_view_search_activate_cb (GtkWidget *search,
+  EmpathyIndividualView *view)
+{
+  GtkTreePath *path;
+  GtkTreeViewColumn *focus_column;
+
+  gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
+  if (path != NULL)
+    {
+      gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
+      gtk_tree_path_free (path);
+
+      gtk_widget_hide (search);
+    }
+}
+
+static void
+individual_view_search_hide_cb (EmpathyLiveSearch *search,
+    EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gboolean valid = FALSE;
+
+  /* block expand or collapse handlers, they would write the
+   * expand or collapsed setting to file otherwise */
+  g_signal_handlers_block_by_func (view,
+      individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+  g_signal_handlers_block_by_func (view,
+    individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+
+  /* restore which groups are expanded and which are not */
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
+  for (valid = gtk_tree_model_get_iter_first (model, &iter);
+       valid; valid = gtk_tree_model_iter_next (model, &iter))
+    {
+      gboolean is_group;
+      gchar *name = NULL;
+      GtkTreePath *path;
+
+      gtk_tree_model_get (model, &iter,
+          EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
+          EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+          -1);
+
+      if (!is_group)
+        {
+          g_free (name);
+          continue;
+        }
+
+      path = gtk_tree_model_get_path (model, &iter);
+      if ((priv->view_features &
+            EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
+          empathy_contact_group_get_expanded (name))
+        {
+          gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
+        }
+      else
+        {
+          gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
+        }
+
+      gtk_tree_path_free (path);
+      g_free (name);
+    }
+
+  /* unblock expand or collapse handlers */
+  g_signal_handlers_unblock_by_func (view,
+      individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+  g_signal_handlers_unblock_by_func (view,
+      individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
+}
+
+static void
+individual_view_search_show_cb (EmpathyLiveSearch *search,
+    EmpathyIndividualView *view)
+{
+  /* block expand or collapse handlers during expand all, they would
+   * write the expand or collapsed setting to file otherwise */
+  g_signal_handlers_block_by_func (view,
+      individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+
+  gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
+
+  g_signal_handlers_unblock_by_func (view,
+      individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
+}
+
+typedef struct {
+  EmpathyIndividualView *view;
+  GtkTreeRowReference *row_ref;
+  gboolean expand;
+} ExpandData;
+
+static gboolean
+individual_view_expand_idle_cb (gpointer user_data)
+{
+  ExpandData *data = user_data;
+  GtkTreePath *path;
+
+  path = gtk_tree_row_reference_get_path (data->row_ref);
+  if (path == NULL)
+    goto done;
+
+  g_signal_handlers_block_by_func (data->view,
+    individual_view_row_expand_or_collapse_cb,
+    GINT_TO_POINTER (data->expand));
+
+  if (data->expand)
+    gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
+  else
+    gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
+
+  gtk_tree_path_free (path);
+
+  g_signal_handlers_unblock_by_func (data->view,
+      individual_view_row_expand_or_collapse_cb,
+      GINT_TO_POINTER (data->expand));
+
+done:
+  g_object_unref (data->view);
+  gtk_tree_row_reference_free (data->row_ref);
+  g_slice_free (ExpandData, data);
+
+  return FALSE;
+}
+
+static void
+individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
+    GtkTreePath *path,
+    GtkTreeIter *iter,
+    EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  gboolean is_group = FALSE;
+  gchar *name = NULL;
+  ExpandData *data;
+
+  gtk_tree_model_get (model, iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
+      -1);
+
+  if (!is_group || EMP_STR_EMPTY (name))
+    {
+      g_free (name);
+      return;
+    }
+
+  data = g_slice_new0 (ExpandData);
+  data->view = g_object_ref (view);
+  data->row_ref = gtk_tree_row_reference_new (model, path);
+  data->expand =
+      (priv->view_features &
+          EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
+      (priv->search_widget != NULL &&
+          gtk_widget_get_visible (priv->search_widget)) ||
+      empathy_contact_group_get_expanded (name);
+
+  /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
+   * gtk_tree_model_filter_refilter () */
+  g_idle_add (individual_view_expand_idle_cb, data);
+
+  g_free (name);
+}
+
+static void
+individual_view_verify_group_visibility (EmpathyIndividualView *view,
+    GtkTreePath *path)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  GtkTreeModel *model;
+  GtkTreePath *parent_path;
+  GtkTreeIter parent_iter;
+
+  if (gtk_tree_path_get_depth (path) < 2)
+    return;
+
+  /* A group row is visible if and only if at least one if its child is visible.
+   * So when a row is inserted/deleted/changed in the base model, that could
+   * modify the visibility of its parent in the filter model.
+  */
+
+  model = GTK_TREE_MODEL (priv->store);
+  parent_path = gtk_tree_path_copy (path);
+  gtk_tree_path_up (parent_path);
+  if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
+    {
+      /* This tells the filter to verify the visibility of that row, and
+       * show/hide it if necessary */
+      gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
+              parent_path, &parent_iter);
+    }
+  gtk_tree_path_free (parent_path);
+}
+
+static void
+individual_view_store_row_changed_cb (GtkTreeModel *model,
+  GtkTreePath *path,
+  GtkTreeIter *iter,
+  EmpathyIndividualView *view)
+{
+  individual_view_verify_group_visibility (view, path);
+}
+
+static void
+individual_view_store_row_deleted_cb (GtkTreeModel *model,
+  GtkTreePath *path,
+  EmpathyIndividualView *view)
+{
+  individual_view_verify_group_visibility (view, path);
+}
+
+static void
+individual_view_constructed (GObject *object)
+{
+  EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  GtkCellRenderer *cell;
+  GtkTreeViewColumn *col;
+  guint i;
+
+  priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
+      GTK_TREE_MODEL (priv->store), NULL));
+  gtk_tree_model_filter_set_visible_func (priv->filter,
+      individual_view_filter_visible_func, view, NULL);
+
+  g_signal_connect (priv->store, "row-has-child-toggled",
+      G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
+  gtk_tree_view_set_model (GTK_TREE_VIEW (view),
+      GTK_TREE_MODEL (priv->filter));
+
+  tp_g_signal_connect_object (priv->store, "row-changed",
+      G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
+  tp_g_signal_connect_object (priv->store, "row-inserted",
+      G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
+  tp_g_signal_connect_object (priv->store, "row-deleted",
+      G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
+
+  tp_g_signal_connect_object (priv->store, "row-changed",
+    G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
+  tp_g_signal_connect_object (priv->store, "row-inserted",
+    G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
+  tp_g_signal_connect_object (priv->store, "row-deleted",
+    G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
+
+  /* Setup view */
+  /* Setting reorderable is a hack that gets us row previews as drag icons
+     for free.  We override all the drag handlers.  It's tricky to get the
+     position of the drag icon right in drag_begin.  GtkTreeView has special
+     voodoo for it, so we let it do the voodoo that he do.
+   */
+  g_object_set (view,
+      "headers-visible", FALSE,
+      "reorderable", TRUE,
+      "show-expanders", FALSE,
+      NULL);
+
+  col = gtk_tree_view_column_new ();
+
+  /* State */
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_tree_view_column_pack_start (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
+      view, NULL);
+
+  g_object_set (cell,
+      "xpad", 5,
+      "ypad", 1,
+      "visible", FALSE,
+      NULL);
+
+  /* Group icon */
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_tree_view_column_pack_start (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
+      view, NULL);
+
+  g_object_set (cell,
+      "xpad", 0,
+      "ypad", 0,
+      "visible", FALSE,
+      "width", 16,
+      "height", 16,
+      NULL);
+
+  /* Name */
+  cell = empathy_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (col, cell, TRUE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
+
+  gtk_tree_view_column_add_attribute (col, cell,
+      "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
+  gtk_tree_view_column_add_attribute (col, cell,
+      "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
+
+  /* Audio Call Icon */
+  cell = empathy_cell_renderer_activatable_new ();
+  gtk_tree_view_column_pack_start (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
+      view, NULL);
+
+  g_object_set (cell, "visible", FALSE, NULL);
+
+  g_signal_connect (cell, "path-activated",
+      G_CALLBACK (individual_view_call_activated_cb), view);
+
+  /* Avatar */
+  cell = gtk_cell_renderer_pixbuf_new ();
+  gtk_tree_view_column_pack_start (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
+      view, NULL);
+
+  g_object_set (cell,
+      "xpad", 0,
+      "ypad", 0,
+      "visible", FALSE,
+      "width", 32,
+      "height", 32,
+      NULL);
+
+  /* Expander */
+  cell = empathy_cell_renderer_expander_new ();
+  gtk_tree_view_column_pack_end (col, cell, FALSE);
+  gtk_tree_view_column_set_cell_data_func (col, cell,
+      (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
+      view, NULL);
+
+  /* Actually add the column now we have added all cell renderers */
+  gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
+
+  /* Drag & Drop. */
+  for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
+    {
+      drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
+    }
+
+  for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
+    {
+      drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
+          FALSE);
+    }
+}
+
+static void
+individual_view_set_view_features (EmpathyIndividualView *view,
+    EmpathyIndividualFeatureFlags features)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  gboolean has_tooltip;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
+
+  priv->view_features = features;
+
+  /* Update DnD source/dest */
+  if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
+    {
+      gtk_drag_source_set (GTK_WIDGET (view),
+          GDK_BUTTON1_MASK,
+          drag_types_source,
+          G_N_ELEMENTS (drag_types_source),
+          GDK_ACTION_MOVE | GDK_ACTION_COPY);
+    }
+  else
+    {
+      gtk_drag_source_unset (GTK_WIDGET (view));
+
+    }
+
+  if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
+    {
+      gtk_drag_dest_set (GTK_WIDGET (view),
+          GTK_DEST_DEFAULT_ALL,
+          drag_types_dest,
+          G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
+    }
+  else
+    {
+      /* FIXME: URI could still be droped depending on FT feature */
+      gtk_drag_dest_unset (GTK_WIDGET (view));
+    }
+
+  /* Update has-tooltip */
+  has_tooltip =
+      (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
+  gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
+}
+
+static void
+individual_view_dispose (GObject *object)
+{
+  EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+
+  tp_clear_object (&priv->store);
+  tp_clear_object (&priv->filter);
+  tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
+  tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
+
+  empathy_individual_view_set_live_search (view, NULL);
+
+  G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
+}
+
+static void
+individual_view_get_property (GObject *object,
+    guint param_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  EmpathyIndividualViewPriv *priv;
+
+  priv = GET_PRIV (object);
+
+  switch (param_id)
+    {
+    case PROP_STORE:
+      g_value_set_object (value, priv->store);
+      break;
+    case PROP_VIEW_FEATURES:
+      g_value_set_flags (value, priv->view_features);
+      break;
+    case PROP_INDIVIDUAL_FEATURES:
+      g_value_set_flags (value, priv->individual_features);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+individual_view_set_property (GObject *object,
+    guint param_id,
+    const GValue *value,
+    GParamSpec *pspec)
+{
+  EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
+  EmpathyIndividualViewPriv *priv = GET_PRIV (object);
+
+  switch (param_id)
+    {
+    case PROP_STORE:
+      priv->store = g_value_dup_object (value);
+      break;
+    case PROP_VIEW_FEATURES:
+      individual_view_set_view_features (view, g_value_get_flags (value));
+      break;
+    case PROP_INDIVIDUAL_FEATURES:
+      priv->individual_features = g_value_get_flags (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+      break;
+    };
+}
+
+static void
+empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
+
+  object_class->constructed = individual_view_constructed;
+  object_class->dispose = individual_view_dispose;
+  object_class->get_property = individual_view_get_property;
+  object_class->set_property = individual_view_set_property;
+
+  widget_class->drag_data_received = individual_view_drag_data_received;
+  widget_class->drag_drop = individual_view_drag_drop;
+  widget_class->drag_begin = individual_view_drag_begin;
+  widget_class->drag_data_get = individual_view_drag_data_get;
+  widget_class->drag_end = individual_view_drag_end;
+  widget_class->drag_motion = individual_view_drag_motion;
+
+  /* We use the class method to let user of this widget to connect to
+   * the signal and stop emission of the signal so the default handler
+   * won't be called. */
+  tree_view_class->row_activated = individual_view_row_activated;
+
+  signals[DRAG_CONTACT_RECEIVED] =
+      g_signal_new ("drag-contact-received",
+      G_OBJECT_CLASS_TYPE (klass),
+      G_SIGNAL_RUN_LAST,
+      0,
+      NULL, NULL,
+      _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
+      G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
+
+  g_object_class_install_property (object_class,
+      PROP_STORE,
+      g_param_spec_object ("store",
+          "The store of the view",
+          "The store of the view",
+          EMPATHY_TYPE_INDIVIDUAL_STORE,
+          G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_VIEW_FEATURES,
+      g_param_spec_flags ("view-features",
+          "Features of the view",
+          "Flags for all enabled features",
+          EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
+          EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
+  g_object_class_install_property (object_class,
+      PROP_INDIVIDUAL_FEATURES,
+      g_param_spec_flags ("individual-features",
+          "Features of the contact menu",
+          "Flags for all enabled features for the menu",
+          EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
+          EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
+
+  g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
+}
+
+static void
+empathy_individual_view_init (EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
+      EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
+
+  view->priv = priv;
+  /* Get saved group states. */
+  empathy_contact_groups_get_all ();
+
+  gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
+      empathy_individual_store_row_separator_func, NULL, NULL);
+
+  /* Set up drag target lists. */
+  priv->file_targets = gtk_target_list_new (drag_types_dest_file,
+      G_N_ELEMENTS (drag_types_dest_file));
+
+  /* Connect to tree view signals rather than override. */
+  g_signal_connect (view, "button-press-event",
+      G_CALLBACK (individual_view_button_press_event_cb), NULL);
+  g_signal_connect (view, "key-press-event",
+      G_CALLBACK (individual_view_key_press_event_cb), NULL);
+  g_signal_connect (view, "row-expanded",
+      G_CALLBACK (individual_view_row_expand_or_collapse_cb),
+      GINT_TO_POINTER (TRUE));
+  g_signal_connect (view, "row-collapsed",
+      G_CALLBACK (individual_view_row_expand_or_collapse_cb),
+      GINT_TO_POINTER (FALSE));
+  g_signal_connect (view, "query-tooltip",
+      G_CALLBACK (individual_view_query_tooltip_cb), NULL);
+}
+
+EmpathyIndividualView *
+empathy_individual_view_new (EmpathyIndividualStore *store,
+    EmpathyIndividualViewFeatureFlags view_features,
+    EmpathyIndividualFeatureFlags individual_features)
+{
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
+
+  return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
+      "store", store,
+      "individual-features", individual_features,
+      "view-features", view_features, NULL);
+}
+
+FolksIndividual *
+empathy_individual_view_dup_selected (EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreeSelection *selection;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  FolksIndividual *individual;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
+
+  priv = GET_PRIV (view);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    return NULL;
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
+
+  return individual;
+}
+
+EmpathyIndividualManagerFlags
+empathy_individual_view_get_flags (EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreeSelection *selection;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  EmpathyIndividualFeatureFlags flags;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
+
+  priv = GET_PRIV (view);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    return 0;
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
+
+  return flags;
+}
+
+gchar *
+empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
+    gboolean *is_fake_group)
+{
+  EmpathyIndividualViewPriv *priv;
+  GtkTreeSelection *selection;
+  GtkTreeIter iter;
+  GtkTreeModel *model;
+  gboolean is_group;
+  gchar *name;
+  gboolean fake;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
+
+  priv = GET_PRIV (view);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
+  if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+    return NULL;
+
+  gtk_tree_model_get (model, &iter,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
+      EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
+      EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
+
+  if (!is_group)
+    {
+      g_free (name);
+      return NULL;
+    }
+
+  if (is_fake_group != NULL)
+    *is_fake_group = fake;
+
+  return name;
+}
+
+static gboolean
+individual_view_remove_dialog_show (GtkWindow *parent,
+    const gchar *message,
+    const gchar *secondary_text)
+{
+  GtkWidget *dialog;
+  gboolean res;
+
+  dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
+      GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
+  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+      GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
+      GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+      "%s", secondary_text);
+
+  gtk_widget_show (dialog);
+
+  res = gtk_dialog_run (GTK_DIALOG (dialog));
+  gtk_widget_destroy (dialog);
+
+  return (res == GTK_RESPONSE_YES);
+}
+
+static void
+individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
+    EmpathyIndividualView *view)
+{
+  gchar *group;
+
+  group = empathy_individual_view_get_selected_group (view, NULL);
+  if (group != NULL)
+    {
+      gchar *text;
+      GtkWindow *parent;
+
+      text =
+          g_strdup_printf (_("Do you really want to remove the group '%s'?"),
+          group);
+      parent = empathy_get_toplevel_window (GTK_WIDGET (view));
+      if (individual_view_remove_dialog_show (parent, _("Removing group"),
+              text))
+        {
+          EmpathyIndividualManager *manager =
+              empathy_individual_manager_dup_singleton ();
+          empathy_individual_manager_remove_group (manager, group);
+          g_object_unref (G_OBJECT (manager));
+        }
+
+      g_free (text);
+    }
+
+  g_free (group);
+}
+
+GtkWidget *
+empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  gchar *group;
+  GtkWidget *menu;
+  GtkWidget *item;
+  GtkWidget *image;
+  gboolean is_fake_group;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
+
+  if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
+              EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
+    return NULL;
+
+  group = empathy_individual_view_get_selected_group (view, &is_fake_group);
+  if (!group || is_fake_group)
+    {
+      /* We can't alter fake groups */
+      return NULL;
+    }
+
+  menu = gtk_menu_new ();
+
+  /* TODO: implement
+     if (priv->view_features &
+     EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
+     item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
+     gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+     gtk_widget_show (item);
+     g_signal_connect (item, "activate",
+     G_CALLBACK (individual_view_group_rename_activate_cb),
+     view);
+     }
+   */
+
+  if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
+    {
+      item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
+      image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
+          GTK_ICON_SIZE_MENU);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+      gtk_widget_show (item);
+      g_signal_connect (item, "activate",
+          G_CALLBACK (individual_view_group_remove_activate_cb), view);
+    }
+
+  g_free (group);
+
+  return menu;
+}
+
+static void
+individual_view_remove_activate_cb (GtkMenuItem *menuitem,
+    EmpathyIndividualView *view)
+{
+  FolksIndividual *individual;
+
+  individual = empathy_individual_view_dup_selected (view);
+
+  if (individual != NULL)
+    {
+      gchar *text;
+      GtkWindow *parent;
+
+      parent = empathy_get_toplevel_window (GTK_WIDGET (view));
+      text =
+          g_strdup_printf (_
+          ("Do you really want to remove the contact '%s'?"),
+          folks_individual_get_alias (individual));
+      if (individual_view_remove_dialog_show (parent, _("Removing contact"),
+              text))
+        {
+          EmpathyIndividualManager *manager;
+
+          manager = empathy_individual_manager_dup_singleton ();
+          empathy_individual_manager_remove (manager, individual, "");
+          g_object_unref (G_OBJECT (manager));
+        }
+
+      g_free (text);
+      g_object_unref (individual);
+    }
+}
+
+GtkWidget *
+empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+  FolksIndividual *individual;
+  GtkWidget *menu = NULL;
+  GtkWidget *item;
+  GtkWidget *image;
+  EmpathyIndividualManagerFlags flags;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
+
+  individual = empathy_individual_view_dup_selected (view);
+  if (individual == NULL)
+    return NULL;
+
+  flags = empathy_individual_view_get_flags (view);
+
+  menu = empathy_individual_menu_new (individual, priv->individual_features);
+
+  /* Remove contact */
+  if (priv->view_features &
+      EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
+      flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
+    {
+
+      /* create the menu if required, or just add a separator */
+      if (menu == NULL)
+        menu = gtk_menu_new ();
+      else
+        {
+          item = gtk_separator_menu_item_new ();
+          gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+          gtk_widget_show (item);
+        }
+
+      /* Remove */
+      item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
+      image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
+          GTK_ICON_SIZE_MENU);
+      gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
+      gtk_widget_show (item);
+      g_signal_connect (item, "activate",
+          G_CALLBACK (individual_view_remove_activate_cb), view);
+    }
+
+  g_object_unref (individual);
+
+  return menu;
+}
+
+void
+empathy_individual_view_set_live_search (EmpathyIndividualView *view,
+    EmpathyLiveSearch *search)
+{
+  EmpathyIndividualViewPriv *priv = GET_PRIV (view);
+
+  /* remove old handlers if old search was not null */
+  if (priv->search_widget != NULL)
+    {
+      g_signal_handlers_disconnect_by_func (view,
+          individual_view_start_search_cb, NULL);
+
+      g_signal_handlers_disconnect_by_func (priv->search_widget,
+          individual_view_search_text_notify_cb, view);
+      g_signal_handlers_disconnect_by_func (priv->search_widget,
+          individual_view_search_activate_cb, view);
+      g_signal_handlers_disconnect_by_func (priv->search_widget,
+          individual_view_search_hide_cb, view);
+      g_signal_handlers_disconnect_by_func (priv->search_widget,
+          individual_view_search_show_cb, view);
+      g_object_unref (priv->search_widget);
+      priv->search_widget = NULL;
+    }
+
+  /* connect handlers if new search is not null */
+  if (search != NULL)
+    {
+      priv->search_widget = g_object_ref (search);
+
+      g_signal_connect (view, "start-interactive-search",
+          G_CALLBACK (individual_view_start_search_cb), NULL);
+
+      g_signal_connect (priv->search_widget, "notify::text",
+          G_CALLBACK (individual_view_search_text_notify_cb), view);
+      g_signal_connect (priv->search_widget, "activate",
+          G_CALLBACK (individual_view_search_activate_cb), view);
+      g_signal_connect (priv->search_widget, "hide",
+          G_CALLBACK (individual_view_search_hide_cb), view);
+      g_signal_connect (priv->search_widget, "show",
+          G_CALLBACK (individual_view_search_show_cb), view);
+    }
+}
diff --git a/libempathy-gtk/empathy-individual-view.h b/libempathy-gtk/empathy-individual-view.h
new file mode 100644
index 0000000..826e490
--- /dev/null
+++ b/libempathy-gtk/empathy-individual-view.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2005-2007 Imendio AB
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA  02110-1301  USA
+ *
+ * Authors: Mikael Hallendal <micke@imendio.com>
+ *          Martyn Russell <martyn@imendio.com>
+ *          Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_VIEW_H__
+#define __EMPATHY_INDIVIDUAL_VIEW_H__
+
+#include <gtk/gtk.h>
+
+#include <folks/folks.h>
+
+#include <libempathy/empathy-enum-types.h>
+
+#include "empathy-live-search.h"
+#include "empathy-individual-menu.h"
+#include "empathy-individual-store.h"
+
+G_BEGIN_DECLS
+#define EMPATHY_TYPE_INDIVIDUAL_VIEW         (empathy_individual_view_get_type ())
+#define EMPATHY_INDIVIDUAL_VIEW(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualView))
+#define EMPATHY_INDIVIDUAL_VIEW_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewClass))
+#define EMPATHY_IS_INDIVIDUAL_VIEW(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_INDIVIDUAL_VIEW))
+#define EMPATHY_IS_INDIVIDUAL_VIEW_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_INDIVIDUAL_VIEW))
+#define EMPATHY_INDIVIDUAL_VIEW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewClass))
+typedef struct _EmpathyIndividualView EmpathyIndividualView;
+typedef struct _EmpathyIndividualViewClass EmpathyIndividualViewClass;
+
+typedef enum
+{
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE = 0,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE = 1 << 0,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME = 1 << 1,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE = 1 << 2,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE = 1 << 3,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP = 1 << 4,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG = 1 << 5,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP = 1 << 6,
+  EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL = (1 << 7) - 1,
+} EmpathyIndividualViewFeatureFlags;
+
+struct _EmpathyIndividualView
+{
+  GtkTreeView parent;
+  gpointer priv;
+};
+
+struct _EmpathyIndividualViewClass
+{
+  GtkTreeViewClass parent_class;
+};
+
+GType empathy_individual_view_get_type (void) G_GNUC_CONST;
+
+EmpathyIndividualView *empathy_individual_view_new (
+    EmpathyIndividualStore *store,
+    EmpathyIndividualViewFeatureFlags view_features,
+    EmpathyIndividualFeatureFlags individual_features);
+
+FolksIndividual *empathy_individual_view_dup_selected (
+    EmpathyIndividualView *view);
+
+EmpathyIndividualManagerFlags empathy_individual_view_get_flags (
+    EmpathyIndividualView *view);
+
+gchar *empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
+    gboolean * is_fake_group);
+
+GtkWidget *empathy_individual_view_get_individual_menu (
+    EmpathyIndividualView *view);
+
+GtkWidget *empathy_individual_view_get_group_menu (EmpathyIndividualView *view);
+
+void empathy_individual_view_set_live_search (EmpathyIndividualView *view,
+    EmpathyLiveSearch *search);
+
+G_END_DECLS
+#endif /* __EMPATHY_INDIVIDUAL_VIEW_H__ */
diff --git a/libempathy-gtk/empathy-log-window.c b/libempathy-gtk/empathy-log-window.c
index afecd75..986a3af 100644
--- a/libempathy-gtk/empathy-log-window.c
+++ b/libempathy-gtk/empathy-log-window.c
@@ -524,10 +524,10 @@ log_manager_searched_new_cb (GObject *manager,
 					continue;
 			}
 
-			g_date_strftime (date_readable, sizeof(date_readable),
+			g_date_strftime (date_readable, sizeof (date_readable),
 				EMPATHY_TIME_FORMAT_DISPLAY_LONG, hit->date);
 
-			g_date_strftime (tmp, sizeof(tmp),
+			g_date_strftime (tmp, sizeof (tmp),
 				"%Y%m%d", hit->date);
 
 			account_name = tp_account_get_display_name (hit->account);
diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c
index 6339a89..40f6689 100644
--- a/libempathy-gtk/empathy-ui-utils.c
+++ b/libempathy-gtk/empathy-ui-utils.c
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2002-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -23,6 +23,7 @@
  *          Martyn Russell <martyn@imendio.com>
  *          Xavier Claessens <xclaesse@gmail.com>
  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
  *
  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
  *          Paolo Maggi
@@ -38,6 +39,9 @@
 #include <gtk/gtk.h>
 #include <gio/gio.h>
 
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
+
 #include "empathy-ui-utils.h"
 #include "empathy-images.h"
 #include "empathy-smiley-manager.h"
@@ -221,6 +225,18 @@ empathy_icon_name_for_contact (EmpathyContact *contact)
 }
 
 const gchar *
+empathy_icon_name_for_individual (FolksIndividual *individual)
+{
+	FolksPresenceType folks_presence;
+	TpConnectionPresenceType presence;
+
+	folks_presence = folks_individual_get_presence_type (individual);
+	presence = empathy_folks_presence_type_to_tp (folks_presence);
+
+	return empathy_icon_name_for_presence (presence);
+}
+
+const gchar *
 empathy_protocol_name_for_contact (EmpathyContact   *contact)
 {
 	TpAccount     *account;
@@ -421,6 +437,35 @@ empathy_gdk_pixbuf_is_opaque (GdkPixbuf *pixbuf)
 	return TRUE;
 }
 
+static GdkPixbuf *
+avatar_pixbuf_from_loader (GdkPixbufLoader *loader)
+{
+	GdkPixbuf *pixbuf;
+
+	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+	if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
+		GdkPixbuf *rounded_pixbuf;
+
+		rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+						 gdk_pixbuf_get_width (pixbuf),
+						 gdk_pixbuf_get_height (pixbuf));
+		gdk_pixbuf_copy_area (pixbuf, 0, 0,
+				      gdk_pixbuf_get_width (pixbuf),
+				      gdk_pixbuf_get_height (pixbuf),
+				      rounded_pixbuf,
+				      0, 0);
+		pixbuf = rounded_pixbuf;
+	} else {
+		g_object_ref (pixbuf);
+	}
+
+	if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
+		empathy_avatar_pixbuf_roundify (pixbuf);
+	}
+
+	return pixbuf;
+}
+
 GdkPixbuf *
 empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
 				  gint          width,
@@ -454,27 +499,7 @@ empathy_pixbuf_from_avatar_scaled (EmpathyAvatar *avatar,
 	}
 
 	gdk_pixbuf_loader_close (loader, NULL);
-
-	pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
-	if (!gdk_pixbuf_get_has_alpha (pixbuf)) {
-		GdkPixbuf *rounded_pixbuf;
-
-		rounded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
-						 gdk_pixbuf_get_width (pixbuf),
-						 gdk_pixbuf_get_height (pixbuf));
-		gdk_pixbuf_copy_area (pixbuf, 0, 0,
-				      gdk_pixbuf_get_width (pixbuf),
-				      gdk_pixbuf_get_height (pixbuf),
-				      rounded_pixbuf,
-				      0, 0);
-		pixbuf = rounded_pixbuf;
-	} else {
-		g_object_ref (pixbuf);
-	}
-
-	if (empathy_gdk_pixbuf_is_opaque (pixbuf)) {
-		empathy_avatar_pixbuf_roundify (pixbuf);
-	}
+	pixbuf = avatar_pixbuf_from_loader (loader);
 
 	g_object_unref (loader);
 
@@ -495,6 +520,160 @@ empathy_pixbuf_avatar_from_contact_scaled (EmpathyContact *contact,
 	return empathy_pixbuf_from_avatar_scaled (avatar, width, height);
 }
 
+typedef struct {
+	FolksIndividual *individual;
+	GSimpleAsyncResult *result;
+	guint width;
+	guint height;
+} PixbufAvatarFromIndividualClosure;
+
+static PixbufAvatarFromIndividualClosure *
+pixbuf_avatar_from_individual_closure_new (FolksIndividual    *individual,
+					   GSimpleAsyncResult *result,
+					   gint                width,
+					   gint                height)
+{
+	PixbufAvatarFromIndividualClosure *closure;
+
+	g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+
+	closure = g_new0 (PixbufAvatarFromIndividualClosure, 1);
+	closure->individual = g_object_ref (individual);
+	closure->result = g_object_ref (result);
+	closure->width = width;
+	closure->height = height;
+
+	return closure;
+}
+
+static void
+pixbuf_avatar_from_individual_closure_free (
+		PixbufAvatarFromIndividualClosure *closure)
+{
+	g_object_unref (closure->individual);
+	g_object_unref (closure->result);
+	g_free (closure);
+}
+
+static void
+avatar_file_load_contents_cb (GObject      *object,
+			      GAsyncResult *result,
+			      gpointer      user_data)
+{
+	GFile *file = G_FILE (object);
+	PixbufAvatarFromIndividualClosure *closure = user_data;
+	char *data;
+	gsize data_size;
+	struct SizeData size_data;
+	GError *error = NULL;
+	GdkPixbufLoader *loader = NULL;
+
+	if (!g_file_load_contents_finish (file, result, &data, &data_size,
+				NULL, &error)) {
+		DEBUG ("failed to load avatar from file: %s",
+				error->message);
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out;
+	}
+
+	size_data.width = closure->width;
+	size_data.height = closure->height;
+	size_data.preserve_aspect_ratio = TRUE;
+
+	loader = gdk_pixbuf_loader_new ();
+
+	g_signal_connect (loader, "size-prepared",
+			  G_CALLBACK (pixbuf_from_avatar_size_prepared_cb),
+			  &size_data);
+
+	if (!gdk_pixbuf_loader_write (loader, (guchar *) data, data_size,
+				&error)) {
+		DEBUG ("Failed to write to pixbuf loader: %s",
+			error ? error->message : "No error given");
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out;
+	}
+	if (!gdk_pixbuf_loader_close (loader, &error)) {
+		DEBUG ("Failed to close pixbuf loader: %s",
+			error ? error->message : "No error given");
+		g_simple_async_result_set_from_error (closure->result, error);
+		goto out;
+	}
+
+	g_simple_async_result_set_op_res_gpointer (closure->result,
+			avatar_pixbuf_from_loader (loader), NULL);
+
+out:
+	g_simple_async_result_complete (closure->result);
+
+	g_clear_error (&error);
+	g_free (data);
+	tp_clear_object (&loader);
+	pixbuf_avatar_from_individual_closure_free (closure);
+}
+
+void
+empathy_pixbuf_avatar_from_individual_scaled_async (
+		FolksIndividual     *individual,
+		GAsyncReadyCallback  callback,
+		gint                 width,
+		gint                 height,
+		gpointer             user_data)
+{
+	GFile *avatar_file;
+	GSimpleAsyncResult *result;
+	PixbufAvatarFromIndividualClosure *closure;
+
+	result = g_simple_async_result_new (G_OBJECT (individual),
+			callback, user_data,
+			empathy_pixbuf_avatar_from_individual_scaled_finish);
+
+	avatar_file = folks_avatar_get_avatar (FOLKS_AVATAR (individual));
+	if (avatar_file == NULL)
+		goto out;
+
+	closure = pixbuf_avatar_from_individual_closure_new (individual, result,
+							     width, height);
+	if (closure == NULL)
+		goto out;
+
+	g_file_load_contents_async (avatar_file, NULL,
+			avatar_file_load_contents_cb, closure);
+
+	g_object_unref (result);
+
+	return;
+
+out:
+	g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
+	g_simple_async_result_complete (result);
+	g_object_unref (result);
+}
+
+GdkPixbuf *
+empathy_pixbuf_avatar_from_individual_scaled_finish (
+		FolksIndividual *individual,
+		GAsyncResult *result,
+		GError **error)
+{
+	GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
+	gboolean result_valid;
+
+	g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+	g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (simple), NULL);
+
+	if (g_simple_async_result_propagate_error (simple, error))
+		return NULL;
+
+	result_valid = g_simple_async_result_is_valid (result,
+			G_OBJECT (individual),
+			empathy_pixbuf_avatar_from_individual_scaled_finish);
+	g_return_val_if_fail (result_valid, NULL);
+
+	return g_simple_async_result_get_op_res_gpointer (simple);
+}
+
 GdkPixbuf *
 empathy_pixbuf_contact_status_icon (EmpathyContact *contact,
 				   gboolean       show_protocol)
@@ -524,7 +703,8 @@ empathy_pixbuf_contact_status_icon_with_icon_name (EmpathyContact *contact,
 	gint       height, width;
 	gint       numerator, denominator;
 
-	g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+	g_return_val_if_fail (EMPATHY_IS_CONTACT (contact) ||
+			(show_protocol == FALSE), NULL);
 	g_return_val_if_fail (icon_name != NULL, NULL);
 
 	numerator = 3;
diff --git a/libempathy-gtk/empathy-ui-utils.h b/libempathy-gtk/empathy-ui-utils.h
index e2f3e4b..87c17b3 100644
--- a/libempathy-gtk/empathy-ui-utils.h
+++ b/libempathy-gtk/empathy-ui-utils.h
@@ -1,7 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2002-2007 Imendio AB
- * Copyright (C) 2007-2008 Collabora Ltd.
+ * Copyright (C) 2007-2010 Collabora Ltd.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -23,6 +23,7 @@
  *          Martyn Russell <martyn@imendio.com>
  *          Xavier Claessens <xclaesse@gmail.com>
  *          Jonny Lamb <jonny.lamb@collabora.co.uk>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
  *
  *          Part of this file is copied from GtkSourceView (gtksourceiter.c):
  *          Paolo Maggi
@@ -34,6 +35,8 @@
 
 #include <gtk/gtk.h>
 
+#include <folks/folks.h>
+
 #include <libempathy/empathy-contact.h>
 #include <libempathy/empathy-ft-handler.h>
 
@@ -46,6 +49,10 @@ G_BEGIN_DECLS
 					    (x) < gdk_screen_width () && \
 					    (y) < gdk_screen_height ())
 
+typedef void (*EmpathyPixbufAvatarFromIndividualCb) (FolksIndividual *individual,
+		GdkPixbuf *pixbuf,
+		gpointer user_data);
+
 void            empathy_gtk_init                        (void);
 
 /* Glade */
@@ -62,12 +69,22 @@ GtkWidget     *empathy_builder_unref_and_keep_widget    (GtkBuilder       *gui,
 /* Pixbufs */
 const gchar * empathy_icon_name_for_presence            (TpConnectionPresenceType  presence);
 const gchar * empathy_icon_name_for_contact             (EmpathyContact   *contact);
+const gchar * empathy_icon_name_for_individual          (FolksIndividual  *individual);
 const gchar * empathy_protocol_name_for_contact         (EmpathyContact   *contact);
 GdkPixbuf *   empathy_pixbuf_from_data                  (gchar            *data,
 							 gsize             data_size);
 GdkPixbuf *   empathy_pixbuf_from_data_and_mime         (gchar            *data,
 							 gsize             data_size,
 							 gchar           **mime_type);
+void empathy_pixbuf_avatar_from_individual_scaled_async (FolksIndividual     *individual,
+							 GAsyncReadyCallback  callback,
+							 gint                 width,
+							 gint                 height,
+							 gpointer             user_data);
+GdkPixbuf * empathy_pixbuf_avatar_from_individual_scaled_finish (
+							 FolksIndividual  *individual,
+							 GAsyncResult     *result,
+							 GError          **error);
 GdkPixbuf *   empathy_pixbuf_from_avatar_scaled         (EmpathyAvatar    *avatar,
 							 gint              width,
 							 gint              height);
diff --git a/libempathy/Makefile.am b/libempathy/Makefile.am
index d44d8af..9020205 100644
--- a/libempathy/Makefile.am
+++ b/libempathy/Makefile.am
@@ -44,6 +44,7 @@ libempathy_headers =				\
 	empathy-gsettings.h			\
 	empathy-handler.h			\
 	empathy-idle.h				\
+	empathy-individual-manager.h		\
 	empathy-irc-network-manager.h		\
 	empathy-irc-network.h			\
 	empathy-irc-server.h			\
@@ -80,6 +81,7 @@ libempathy_la_SOURCES =					\
 	empathy-ft-handler.c				\
 	empathy-handler.c				\
 	empathy-idle.c					\
+	empathy-individual-manager.c			\
 	empathy-irc-network-manager.c			\
 	empathy-irc-network.c				\
 	empathy-irc-server.c				\
diff --git a/libempathy/empathy-individual-manager.c b/libempathy/empathy-individual-manager.c
new file mode 100644
index 0000000..de0add4
--- /dev/null
+++ b/libempathy/empathy-individual-manager.c
@@ -0,0 +1,405 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/proxy-subclass.h>
+#include <telepathy-glib/util.h>
+
+#include <folks/folks.h>
+
+#include <extensions/extensions.h>
+
+#include "empathy-individual-manager.h"
+#include "empathy-contact-manager.h"
+#include "empathy-contact-list.h"
+#include "empathy-marshal.h"
+#include "empathy-utils.h"
+
+#define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
+#include "empathy-debug.h"
+
+#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualManager)
+
+/* This class doesn't store or ref any of the individuals, since they're already
+ * stored and referenced in the aggregator.
+ *
+ * This class merely forwards along signals from the aggregator and individuals
+ * and wraps aggregator functions for other client code. */
+typedef struct
+{
+  FolksIndividualAggregator *aggregator;
+  EmpathyContactManager *contact_manager;
+} EmpathyIndividualManagerPriv;
+
+enum
+{
+  FAVOURITES_CHANGED,
+  GROUPS_CHANGED,
+  MEMBERS_CHANGED,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (EmpathyIndividualManager, empathy_individual_manager,
+    G_TYPE_OBJECT);
+
+static EmpathyIndividualManager *manager_singleton = NULL;
+
+static void
+individual_group_changed_cb (FolksIndividual *individual,
+    gchar *group,
+    gboolean is_member,
+    EmpathyIndividualManager *self)
+{
+  g_signal_emit (self, signals[GROUPS_CHANGED], 0, individual, group,
+      is_member);
+}
+
+static void
+individual_notify_is_favourite_cb (FolksIndividual *individual,
+    GParamSpec *pspec,
+    EmpathyIndividualManager *self)
+{
+  gboolean is_favourite = folks_favourite_get_is_favourite (
+      FOLKS_FAVOURITE (individual));
+  g_signal_emit (self, signals[FAVOURITES_CHANGED], 0, individual,
+      is_favourite);
+}
+
+static void
+aggregator_individuals_changed_cb (FolksIndividualAggregator *aggregator,
+    GList *added,
+    GList *removed,
+    const char *message,
+    FolksPersona *actor,
+    guint reason,
+    EmpathyIndividualManager *self)
+{
+  GList *l;
+
+  for (l = added; l; l = l->next)
+    {
+      g_signal_connect (l->data, "group-changed",
+          G_CALLBACK (individual_group_changed_cb), self);
+      g_signal_connect (l->data, "notify::is-favourite",
+          G_CALLBACK (individual_notify_is_favourite_cb), self);
+    }
+
+  for (l = removed; l; l = l->next)
+    {
+      g_signal_handlers_disconnect_by_func (l->data,
+          individual_group_changed_cb, self);
+      g_signal_handlers_disconnect_by_func (l->data,
+          individual_notify_is_favourite_cb, self);
+    }
+
+  g_signal_emit (self, signals[MEMBERS_CHANGED], 0, message,
+      added, removed,
+      tp_chanel_group_change_reason_from_folks_groups_change_reason (reason),
+      TRUE);
+}
+
+static void
+individual_manager_dispose (GObject *object)
+{
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (object);
+
+  tp_clear_object (&priv->contact_manager);
+  tp_clear_object (&priv->aggregator);
+
+  G_OBJECT_CLASS (empathy_individual_manager_parent_class)->dispose (object);
+}
+
+static GObject *
+individual_manager_constructor (GType type,
+    guint n_props,
+    GObjectConstructParam *props)
+{
+  GObject *retval;
+
+  if (manager_singleton)
+    {
+      retval = g_object_ref (manager_singleton);
+    }
+  else
+    {
+      retval =
+          G_OBJECT_CLASS (empathy_individual_manager_parent_class)->
+          constructor (type, n_props, props);
+
+      manager_singleton = EMPATHY_INDIVIDUAL_MANAGER (retval);
+      g_object_add_weak_pointer (retval, (gpointer) & manager_singleton);
+    }
+
+  return retval;
+}
+
+/**
+ * empathy_individual_manager_initialized:
+ *
+ * Reports whether or not the singleton has already been created.
+ *
+ * There can be instances where you want to access the #EmpathyIndividualManager
+ * only if it has been set up for this process.
+ *
+ * Returns: %TRUE if the #EmpathyIndividualManager singleton has previously
+ * been initialized.
+ */
+gboolean
+empathy_individual_manager_initialized (void)
+{
+  return (manager_singleton != NULL);
+}
+
+static void
+empathy_individual_manager_class_init (EmpathyIndividualManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = individual_manager_dispose;
+  object_class->constructor = individual_manager_constructor;
+
+  signals[GROUPS_CHANGED] =
+      g_signal_new ("groups-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          _empathy_marshal_VOID__OBJECT_STRING_BOOLEAN,
+          G_TYPE_NONE, 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_STRING, G_TYPE_BOOLEAN);
+
+  signals[FAVOURITES_CHANGED] =
+      g_signal_new ("favourites-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          _empathy_marshal_VOID__OBJECT_BOOLEAN,
+          G_TYPE_NONE, 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN);
+
+  signals[MEMBERS_CHANGED] =
+      g_signal_new ("members-changed",
+          G_TYPE_FROM_CLASS (klass),
+          G_SIGNAL_RUN_LAST,
+          0,
+          NULL, NULL,
+          _empathy_marshal_VOID__STRING_OBJECT_OBJECT_UINT,
+          G_TYPE_NONE,
+          4, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_POINTER, G_TYPE_UINT);
+
+  g_type_class_add_private (object_class,
+      sizeof (EmpathyIndividualManagerPriv));
+}
+
+static void
+empathy_individual_manager_init (EmpathyIndividualManager *self)
+{
+  EmpathyIndividualManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerPriv);
+
+  self->priv = priv;
+  priv->contact_manager = empathy_contact_manager_dup_singleton ();
+
+  priv->aggregator = folks_individual_aggregator_new ();
+  g_signal_connect (priv->aggregator, "individuals-changed",
+      G_CALLBACK (aggregator_individuals_changed_cb), self);
+}
+
+EmpathyIndividualManager *
+empathy_individual_manager_dup_singleton (void)
+{
+  return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MANAGER, NULL);
+}
+
+GList *
+empathy_individual_manager_get_members (EmpathyIndividualManager *self)
+{
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+  GHashTable *individuals;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
+  return individuals ? g_hash_table_get_values (individuals) : NULL;
+}
+
+FolksIndividual *
+empathy_individual_manager_lookup_member (EmpathyIndividualManager *self,
+    const gchar *id)
+{
+  EmpathyIndividualManagerPriv *priv = GET_PRIV (self);
+  GHashTable *individuals;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self), NULL);
+
+  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
+  if (individuals != NULL)
+    return g_hash_table_lookup (individuals, id);
+
+  return NULL;
+}
+
+static void
+aggregator_add_persona_from_details_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (source);
+  EmpathyContact *contact = EMPATHY_CONTACT (user_data);
+  FolksPersona *persona;
+  GError *error = NULL;
+
+  persona = folks_individual_aggregator_add_persona_from_details_finish (
+      aggregator, result, &error);
+  if (error != NULL)
+    {
+      g_warning ("failed to add individual from contact: %s", error->message);
+      g_clear_error (&error);
+    }
+
+  /* We can unref the contact now */
+  g_object_unref (contact);
+}
+
+void
+empathy_individual_manager_add_from_contact (EmpathyIndividualManager *self,
+    EmpathyContact *contact)
+{
+  EmpathyIndividualManagerPriv *priv;
+  GHashTable* details;
+  TpAccount *account;
+  GValue value = {0};
+  const gchar *store_id;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  priv = GET_PRIV (self);
+
+  /* We need to ref the contact since otherwise its linked TpHandle will be
+   * destroyed. */
+  g_object_ref (contact);
+
+  DEBUG ("adding individual from contact %s (%s)",
+      empathy_contact_get_id (contact), empathy_contact_get_name (contact));
+
+  account = empathy_contact_get_account (contact);
+  store_id = tp_proxy_get_object_path (TP_PROXY (account));
+
+  details = g_hash_table_new (g_str_hash, g_str_equal);
+  g_value_init (&value, G_TYPE_STRING);
+  g_value_set_string (&value, empathy_contact_get_id (contact));
+  g_hash_table_insert (details, "contact", &value);
+
+  folks_individual_aggregator_add_persona_from_details (
+      priv->aggregator, NULL, "telepathy", store_id, details,
+      aggregator_add_persona_from_details_cb, contact);
+
+  g_hash_table_destroy (details);
+}
+
+/**
+ * Removes the inner contact from the server (and thus the Individual). Not
+ * meant for de-shelling inner personas from an Individual.
+ */
+void
+empathy_individual_manager_remove (EmpathyIndividualManager *self,
+    FolksIndividual *individual,
+    const gchar *message)
+{
+  EmpathyIndividualManagerPriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self));
+  g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
+
+  priv = GET_PRIV (self);
+
+  DEBUG ("removing individual %s (%s)",
+      folks_individual_get_id (individual),
+      folks_individual_get_alias (individual));
+
+  folks_individual_aggregator_remove_individual (priv->aggregator, individual);
+}
+
+static void
+remove_group_cb (const gchar *id,
+    FolksIndividual *individual,
+    const gchar *group)
+{
+  folks_groups_change_group (FOLKS_GROUPS (individual), group, FALSE);
+}
+
+void
+empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
+    const gchar *group)
+{
+  EmpathyIndividualManagerPriv *priv;
+  GHashTable *individuals;
+
+  g_return_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (manager));
+  g_return_if_fail (group != NULL);
+
+  priv = GET_PRIV (manager);
+
+  DEBUG ("removing group %s", group);
+
+  /* Remove every individual from the group */
+  individuals = folks_individual_aggregator_get_individuals (priv->aggregator);
+  g_hash_table_foreach (individuals, (GHFunc) remove_group_cb,
+      (gpointer) group);
+}
+
+EmpathyIndividualManagerFlags
+empathy_individual_manager_get_flags_for_connection (
+    EmpathyIndividualManager *self,
+    TpConnection *connection)
+{
+  EmpathyIndividualManagerPriv *priv;
+  EmpathyContactListFlags list_flags;
+  EmpathyIndividualManagerFlags flags;
+
+  g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_MANAGER (self),
+      EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS);
+
+  priv = GET_PRIV (self);
+
+  list_flags = empathy_contact_manager_get_flags_for_connection (
+    priv->contact_manager, connection);
+
+  flags = EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS;
+  if (list_flags & EMPATHY_CONTACT_LIST_CAN_ADD)
+    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD;
+  if (list_flags & EMPATHY_CONTACT_LIST_CAN_REMOVE)
+    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE;
+  if (list_flags & EMPATHY_CONTACT_LIST_CAN_ALIAS)
+    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS;
+  if (list_flags & EMPATHY_CONTACT_LIST_CAN_GROUP)
+    flags |= EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP;
+
+  return flags;
+}
diff --git a/libempathy/empathy-individual-manager.h b/libempathy/empathy-individual-manager.h
new file mode 100644
index 0000000..66014bc
--- /dev/null
+++ b/libempathy/empathy-individual-manager.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2007-2010 Collabora Ltd.
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Authors: Xavier Claessens <xclaesse@gmail.com>
+ *          Travis Reitter <travis.reitter@collabora.co.uk>
+ */
+
+#ifndef __EMPATHY_INDIVIDUAL_MANAGER_H__
+#define __EMPATHY_INDIVIDUAL_MANAGER_H__
+
+#include <glib.h>
+#include <folks/folks.h>
+
+#include "empathy-contact.h"
+
+G_BEGIN_DECLS
+#define EMPATHY_TYPE_INDIVIDUAL_MANAGER         (empathy_individual_manager_get_type ())
+#define EMPATHY_INDIVIDUAL_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManager))
+#define EMPATHY_INDIVIDUAL_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerClass))
+#define EMPATHY_IS_INDIVIDUAL_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_INDIVIDUAL_MANAGER))
+#define EMPATHY_IS_INDIVIDUAL_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_INDIVIDUAL_MANAGER))
+#define EMPATHY_INDIVIDUAL_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_INDIVIDUAL_MANAGER, EmpathyIndividualManagerClass))
+    typedef enum
+{
+  EMPATHY_INDIVIDUAL_MANAGER_NO_FLAGS = 0,
+  EMPATHY_INDIVIDUAL_MANAGER_CAN_ADD = 1 << 0,
+  EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE = 1 << 1,
+  EMPATHY_INDIVIDUAL_MANAGER_CAN_ALIAS = 1 << 2,
+  EMPATHY_INDIVIDUAL_MANAGER_CAN_GROUP = 1 << 3,
+} EmpathyIndividualManagerFlags;
+
+typedef struct _EmpathyIndividualManager EmpathyIndividualManager;
+typedef struct _EmpathyIndividualManagerClass EmpathyIndividualManagerClass;
+
+struct _EmpathyIndividualManager
+{
+  GObject parent;
+  gpointer priv;
+};
+
+struct _EmpathyIndividualManagerClass
+{
+  GObjectClass parent_class;
+};
+
+GType empathy_individual_manager_get_type (void) G_GNUC_CONST;
+
+gboolean empathy_individual_manager_initialized (void);
+
+EmpathyIndividualManager *empathy_individual_manager_dup_singleton (void);
+
+GList *empathy_individual_manager_get_members (
+    EmpathyIndividualManager *manager);
+
+FolksIndividual *empathy_individual_manager_lookup_member (
+    EmpathyIndividualManager *manager,
+    const gchar *id);
+
+void empathy_individual_manager_add_from_contact (
+    EmpathyIndividualManager *manager,
+    EmpathyContact *contact);
+
+void empathy_individual_manager_remove (EmpathyIndividualManager *manager,
+    FolksIndividual *individual,
+    const gchar *message);
+
+void empathy_individual_manager_remove_group (EmpathyIndividualManager *manager,
+    const gchar *group);
+
+EmpathyIndividualManagerFlags
+empathy_individual_manager_get_flags_for_connection (
+    EmpathyIndividualManager *manager,
+    TpConnection *connection);
+
+G_END_DECLS
+#endif /* __EMPATHY_INDIVIDUAL_MANAGER_H__ */
diff --git a/libempathy/empathy-utils.c b/libempathy/empathy-utils.c
index 1c2d95b..d460f55 100644
--- a/libempathy/empathy-utils.c
+++ b/libempathy/empathy-utils.c
@@ -32,6 +32,9 @@
 
 #include <libxml/uri.h>
 
+#include <folks/folks.h>
+#include <folks/folks-telepathy.h>
+
 #include <telepathy-glib/account-manager.h>
 #include <telepathy-glib/connection.h>
 #include <telepathy-glib/channel.h>
@@ -40,10 +43,12 @@
 
 #include "empathy-utils.h"
 #include "empathy-contact-manager.h"
+#include "empathy-individual-manager.h"
 #include "empathy-dispatcher.h"
 #include "empathy-dispatch-operation.h"
 #include "empathy-idle.h"
 #include "empathy-tp-call.h"
+#include "empathy-tp-contact-factory.h"
 
 #include <extensions/extensions.h>
 
@@ -558,3 +563,99 @@ empathy_connect_new_account (TpAccount *account,
         break;
     }
 }
+
+/* Translate Folks' general presence type to the Tp presence type */
+TpConnectionPresenceType
+empathy_folks_presence_type_to_tp (FolksPresenceType type)
+{
+  return (TpConnectionPresenceType) type;
+}
+
+/* Returns TRUE if the given Individual contains a TpContact */
+gboolean
+empathy_folks_individual_contains_contact (FolksIndividual *individual)
+{
+  GList *personas, *l;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), FALSE);
+
+  personas = folks_individual_get_personas (individual);
+  for (l = personas; l != NULL; l = l->next)
+    {
+      if (TPF_IS_PERSONA (l->data))
+        return (tpf_persona_get_contact (TPF_PERSONA (l->data)) != NULL);
+    }
+
+  return FALSE;
+}
+
+/* TODO: this needs to be eliminated (and replaced in some cases with user
+ * prompts) when we break the assumption that FolksIndividuals are 1:1 with
+ * TpContacts */
+
+/* Retrieve the EmpathyContact corresponding to the first TpContact contained
+ * within the given Individual. Note that this is a temporary convenience. See
+ * the TODO above. */
+EmpathyContact *
+empathy_contact_dup_from_folks_individual (FolksIndividual *individual)
+{
+  GList *personas, *l;
+  EmpathyContact *contact = NULL;
+
+  g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
+
+  personas = folks_individual_get_personas (individual);
+  for (l = personas; (l != NULL) && (contact == NULL); l = l->next)
+    {
+      TpfPersona *persona = l->data;
+
+      if (TPF_IS_PERSONA (persona))
+        {
+          TpContact *tp_contact;
+
+          tp_contact = tpf_persona_get_contact (persona);
+          contact = empathy_contact_dup_from_tp_contact (tp_contact);
+        }
+    }
+
+  return contact;
+}
+
+/* TODO: This also needs to be eliminated, and is horrifically slow. */
+
+/* Retrieve the first Individual containing a TpContact that corresponds to the
+ * given EmpathyContact. Note that this is a temporary convenience. See
+ * the TODO above. */
+FolksIndividual *
+folks_individual_dup_from_empathy_contact (EmpathyContact *contact)
+{
+  EmpathyIndividualManager *manager;
+  FolksIndividual *individual = NULL;
+  GList *individuals, *l;
+
+  manager = empathy_individual_manager_dup_singleton ();
+  individuals = empathy_individual_manager_get_members (manager);
+
+  for (l = individuals; (l != NULL) && (individual == NULL); l = l->next)
+    {
+      FolksIndividual *i = FOLKS_INDIVIDUAL (l->data);
+      EmpathyContact *c = empathy_contact_dup_from_folks_individual (i);
+
+      if (c == contact)
+        individual = g_object_ref (i);
+
+      g_object_unref (c);
+    }
+
+  g_list_free (individuals);
+  g_object_unref (manager);
+
+  return individual;
+}
+
+TpChannelGroupChangeReason
+tp_chanel_group_change_reason_from_folks_groups_change_reason (
+    FolksGroupsChangeReason reason)
+{
+  return (TpChannelGroupChangeReason) reason;
+}
diff --git a/libempathy/empathy-utils.h b/libempathy/empathy-utils.h
index 272b66f..f35d9e5 100644
--- a/libempathy/empathy-utils.h
+++ b/libempathy/empathy-utils.h
@@ -31,6 +31,7 @@
 
 #include <libxml/parser.h>
 #include <libxml/tree.h>
+#include <folks/folks.h>
 #include <telepathy-glib/account-manager.h>
 
 #include "empathy-contact.h"
@@ -89,6 +90,12 @@ gboolean empathy_account_manager_get_accounts_connected (gboolean *connecting);
 void empathy_connect_new_account (TpAccount *account,
     TpAccountManager *account_manager);
 
+TpConnectionPresenceType empathy_folks_presence_type_to_tp (FolksPresenceType type);
+gboolean empathy_folks_individual_contains_contact (FolksIndividual *individual);
+EmpathyContact * empathy_contact_dup_from_folks_individual (FolksIndividual *individual);
+FolksIndividual *folks_individual_dup_from_empathy_contact (EmpathyContact *contact);
+TpChannelGroupChangeReason tp_chanel_group_change_reason_from_folks_groups_change_reason (FolksGroupsChangeReason reason);
+
 G_END_DECLS
 
 #endif /*  __EMPATHY_UTILS_H__ */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e651179..a7bee8d 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -37,6 +37,9 @@ libempathy-gtk/empathy-contact-menu.c
 libempathy-gtk/empathy-contact-selector.c
 libempathy-gtk/empathy-contact-widget.c
 [type: gettext/glade]libempathy-gtk/empathy-contact-widget.ui
+libempathy-gtk/empathy-individual-dialogs.c
+libempathy-gtk/empathy-individual-menu.c
+libempathy-gtk/empathy-individual-view.c
 libempathy-gtk/empathy-irc-network-dialog.c
 libempathy-gtk/empathy-log-window.c
 [type: gettext/glade]libempathy-gtk/empathy-log-window.ui
@@ -63,6 +66,7 @@ src/empathy-account-assistant.c
 src/empathy-accounts-dialog.c
 [type: gettext/glade]src/empathy-accounts-dialog.ui
 src/empathy-auto-salut-account-helper.c
+src/empathy-av.c
 src/empathy-call-window.c
 [type: gettext/glade]src/empathy-call-window.ui
 src/empathy-chat-window.c
diff --git a/src/empathy-main-window.c b/src/empathy-main-window.c
index 5656124..d0f477f 100644
--- a/src/empathy-main-window.c
+++ b/src/empathy-main-window.c
@@ -30,6 +30,8 @@
 #include <glib/gi18n.h>
 
 #include <telepathy-glib/account-manager.h>
+#include <telepathy-glib/util.h>
+#include <folks/folks.h>
 
 #include <libempathy/empathy-contact.h>
 #include <libempathy/empathy-idle.h>
@@ -40,7 +42,10 @@
 #include <libempathy/empathy-contact-list.h>
 #include <libempathy/empathy-contact-manager.h>
 #include <libempathy/empathy-gsettings.h>
+#include <libempathy/empathy-individual-manager.h>
+#include <libempathy/empathy-gsettings.h>
 #include <libempathy/empathy-status-presets.h>
+#include <libempathy/empathy-tp-contact-factory.h>
 
 #include <libempathy-gtk/empathy-contact-dialogs.h>
 #include <libempathy-gtk/empathy-contact-list-store.h>
@@ -48,6 +53,9 @@
 #include <libempathy-gtk/empathy-live-search.h>
 #include <libempathy-gtk/empathy-geometry.h>
 #include <libempathy-gtk/empathy-gtk-enum-types.h>
+#include <libempathy-gtk/empathy-individual-dialogs.h>
+#include <libempathy-gtk/empathy-individual-store.h>
+#include <libempathy-gtk/empathy-individual-view.h>
 #include <libempathy-gtk/empathy-new-message-dialog.h>
 #include <libempathy-gtk/empathy-new-call-dialog.h>
 #include <libempathy-gtk/empathy-log-window.h>
@@ -85,11 +93,11 @@
 
 G_DEFINE_TYPE (EmpathyMainWindow, empathy_main_window, GTK_TYPE_WINDOW);
 
-#define GET_PRIV(self) ((EmpathyMainWindowPriv*)((EmpathyMainWindow*)self)->priv)
+#define GET_PRIV(self) ((EmpathyMainWindowPriv *) ((EmpathyMainWindow *) self)->priv)
 
 struct _EmpathyMainWindowPriv {
-	EmpathyContactListView  *list_view;
-	EmpathyContactListStore *list_store;
+	EmpathyIndividualStore  *individual_store;
+	EmpathyIndividualView   *individual_view;
 	TpAccountManager        *account_manager;
 	EmpathyChatroomManager  *chatroom_manager;
 	EmpathyEventManager     *event_manager;
@@ -165,23 +173,24 @@ main_window_flash_foreach (GtkTreeModel *model,
 			   gpointer      user_data)
 {
 	FlashForeachData *data = (FlashForeachData *) user_data;
+	FolksIndividual *individual;
 	EmpathyContact   *contact;
 	const gchar      *icon_name;
 	GtkTreePath      *parent_path = NULL;
 	GtkTreeIter       parent_iter;
 	GdkPixbuf        *pixbuf = NULL;
 
-	/* To be used with gtk_tree_model_foreach, update the status icon
-	 * of the contact to show the event icon (on=TRUE) or the presence
-	 * (on=FALSE) */
 	gtk_tree_model_get (model, iter,
-			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
+			    EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
+				&individual,
 			    -1);
 
+	if (individual == NULL)
+		return FALSE;
+
+	contact = empathy_contact_dup_from_folks_individual (individual);
 	if (contact != data->event->contact) {
-		if (contact) {
-			g_object_unref (contact);
-		}
+		tp_clear_object (&contact);
 		return FALSE;
 	}
 
@@ -189,13 +198,13 @@ main_window_flash_foreach (GtkTreeModel *model,
 		icon_name = data->event->icon_name;
 		pixbuf = empathy_pixbuf_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
 	} else {
-		pixbuf = contact_list_store_get_contact_status_icon (
-						GET_PRIV (data->window)->list_store,
-						contact);
+		pixbuf = empathy_individual_store_get_individual_status_icon (
+						GET_PRIV (data->window)->individual_store,
+						individual);
 	}
 
 	gtk_tree_store_set (GTK_TREE_STORE (model), iter,
-			    EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf,
+			    EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, pixbuf,
 			    -1);
 
 	/* To make sure the parent is shown correctly, we emit
@@ -210,7 +219,8 @@ main_window_flash_foreach (GtkTreeModel *model,
 		gtk_tree_path_free (parent_path);
 	}
 
-	g_object_unref (contact);
+	g_object_unref (individual);
+	tp_clear_object (&contact);
 
 	return FALSE;
 }
@@ -226,7 +236,7 @@ main_window_flash_cb (EmpathyMainWindow *window)
 
 	priv->flash_on = !priv->flash_on;
 	data.on = priv->flash_on;
-	model = GTK_TREE_MODEL (priv->list_store);
+	model = GTK_TREE_MODEL (priv->individual_store);
 
 	events = empathy_event_manager_get_events (priv->event_manager);
 	for (l = events; l; l = l->next) {
@@ -290,7 +300,7 @@ main_window_event_removed_cb (EmpathyEventManager *manager,
 	data.on = FALSE;
 	data.event = event;
 	data.window = window;
-	gtk_tree_model_foreach (GTK_TREE_MODEL (priv->list_store),
+	gtk_tree_model_foreach (GTK_TREE_MODEL (priv->individual_store),
 				main_window_flash_foreach,
 				&data);
 }
@@ -302,19 +312,26 @@ main_window_row_activated_cb (EmpathyContactListView *view,
 			      EmpathyMainWindow      *window)
 {
 	EmpathyMainWindowPriv *priv = GET_PRIV (window);
-	EmpathyContact *contact;
+	EmpathyContact *contact = NULL;
+	FolksIndividual *individual;
 	GtkTreeModel   *model;
 	GtkTreeIter     iter;
 	GSList         *events, *l;
 
-	model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->list_view));
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->individual_view));
 	gtk_tree_model_get_iter (model, &iter, path);
+
 	gtk_tree_model_get (model, &iter,
-			    EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
+			    EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL,
+				&individual,
 			    -1);
 
+	if (individual != NULL) {
+		contact = empathy_contact_dup_from_folks_individual (individual);
+	}
+
 	if (!contact) {
-		return;
+		goto OUT;
 	}
 
 	/* If the contact has an event activate it, otherwise the
@@ -335,6 +352,8 @@ main_window_row_activated_cb (EmpathyContactListView *view,
 	}
 
 	g_object_unref (contact);
+OUT:
+	tp_clear_object (&individual);
 }
 
 static void
@@ -617,7 +636,7 @@ empathy_main_window_finalize (GObject *window)
 	g_list_free (priv->actions_connected);
 
 	g_object_unref (priv->account_manager);
-	g_object_unref (priv->list_store);
+	g_object_unref (priv->individual_store);
 	g_hash_table_destroy (priv->errors);
 
 	/* disconnect all handlers of status-changed signal */
@@ -693,7 +712,7 @@ static void
 main_window_chat_add_contact_cb (GtkAction         *action,
 				 EmpathyMainWindow *window)
 {
-	empathy_new_contact_dialog_show (GTK_WINDOW (window));
+	empathy_new_individual_dialog_show (GTK_WINDOW (window));
 }
 
 static void
@@ -717,7 +736,8 @@ main_window_view_show_offline_cb (GtkToggleAction   *action,
 
 	/* Turn off sound just while we alter the contact list. */
 	// FIXME: empathy_sound_set_enabled (FALSE);
-	empathy_contact_list_store_set_show_offline (priv->list_store, current);
+	empathy_individual_store_set_show_offline (priv->individual_store,
+			current);
 	//empathy_sound_set_enabled (TRUE);
 }
 
@@ -736,7 +756,7 @@ main_window_notify_sort_contact_cb (GSettings         *gsettings,
 		GEnumClass *enum_class;
 		GEnumValue *enum_value;
 
-		type = empathy_contact_list_store_sort_get_type ();
+		type = empathy_individual_store_sort_get_type ();
 		enum_class = G_ENUM_CLASS (g_type_class_peek (type));
 		enum_value = g_enum_get_value_by_nick (enum_class, str);
 		if (enum_value) {
@@ -768,7 +788,7 @@ main_window_view_sort_contacts_cb (GtkRadioAction    *action,
 	group = gtk_radio_action_get_group (action);
 
 	/* Get string from index */
-	type = empathy_contact_list_store_sort_get_type ();
+	type = empathy_individual_store_sort_get_type ();
 	enum_class = G_ENUM_CLASS (g_type_class_peek (type));
 	enum_value = g_enum_get_value (enum_class, g_slist_index (group, current));
 
@@ -780,7 +800,8 @@ main_window_view_sort_contacts_cb (GtkRadioAction    *action,
 				       EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM,
 				       enum_value->value_nick);
 	}
-	empathy_contact_list_store_set_sort_criterium (priv->list_store, value);
+	empathy_individual_store_set_sort_criterium (priv->individual_store,
+			value);
 }
 
 static void
@@ -795,8 +816,8 @@ main_window_view_show_protocols_cb (GtkToggleAction   *action,
 	g_settings_set_boolean (priv->gsettings_ui,
 				EMPATHY_PREFS_UI_SHOW_PROTOCOLS,
 				value);
-	empathy_contact_list_store_set_show_protocols (priv->list_store,
-						       value);
+	empathy_individual_store_set_show_protocols (priv->individual_store,
+						     value);
 }
 
 /* Matches GtkRadioAction values set in empathy-main-window.ui */
@@ -830,10 +851,11 @@ main_window_view_contacts_list_size_cb (GtkRadioAction    *action,
 				value == CONTACT_LIST_COMPACT_SIZE);
 	g_settings_apply (gsettings_ui);
 
-	empathy_contact_list_store_set_show_avatars (priv->list_store,
-						     value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
-	empathy_contact_list_store_set_is_compact (priv->list_store,
-						   value == CONTACT_LIST_COMPACT_SIZE);
+	/* FIXME: these enums probably have the wrong namespace */
+	empathy_individual_store_set_show_avatars (priv->individual_store,
+			value == CONTACT_LIST_NORMAL_SIZE_WITH_AVATARS);
+	empathy_individual_store_set_is_compact (priv->individual_store,
+			value == CONTACT_LIST_COMPACT_SIZE);
 
 	g_object_unref (gsettings_ui);
 }
@@ -1144,7 +1166,8 @@ main_window_edit_cb (GtkAction         *action,
 	GtkWidget *submenu;
 
 	/* FIXME: It should use the UIManager to merge the contact/group submenu */
-	submenu = empathy_contact_list_view_get_contact_menu (priv->list_view);
+	submenu = empathy_individual_view_get_individual_menu (
+			priv->individual_view);
 	if (submenu) {
 		GtkMenuItem *item;
 		GtkWidget   *label;
@@ -1161,7 +1184,8 @@ main_window_edit_cb (GtkAction         *action,
 		return;
 	}
 
-	submenu = empathy_contact_list_view_get_group_menu (priv->list_view);
+	submenu = empathy_individual_view_get_group_menu (
+			priv->individual_view);
 	if (submenu) {
 		GtkMenuItem *item;
 		GtkWidget   *label;
@@ -1458,6 +1482,7 @@ empathy_main_window_init (EmpathyMainWindow *window)
 {
 	EmpathyMainWindowPriv    *priv;
 	EmpathyContactList       *list_iface;
+	EmpathyIndividualManager *individual_manager;
 	GtkBuilder               *gui;
 	GtkWidget                *sw;
 	GtkToggleAction          *show_offline_widget;
@@ -1595,33 +1620,36 @@ empathy_main_window_init (EmpathyMainWindow *window)
 	priv->throbber_tool_item = GTK_WIDGET (item);
 
 	list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_dup_singleton ());
-	priv->list_store = empathy_contact_list_store_new (list_iface);
-	priv->list_view = empathy_contact_list_view_new (priv->list_store,
-							 EMPATHY_CONTACT_LIST_FEATURE_ALL,
-							 EMPATHY_CONTACT_FEATURE_ALL);
+	individual_manager = empathy_individual_manager_dup_singleton ();
+	priv->individual_store = empathy_individual_store_new (
+			individual_manager);
+	priv->individual_view = empathy_individual_view_new (
+			priv->individual_store,
+			EMPATHY_INDIVIDUAL_VIEW_FEATURE_ALL,
+			EMPATHY_INDIVIDUAL_FEATURE_ALL);
 
 	priv->butterfly_log_migration_members_changed_id = g_signal_connect (
-		list_iface, "members-changed",
-		G_CALLBACK (main_window_members_changed_cb), window);
+			list_iface, "members-changed",
+			G_CALLBACK (main_window_members_changed_cb), window);
 
 	g_object_unref (list_iface);
 
-	gtk_widget_show (GTK_WIDGET (priv->list_view));
+	gtk_widget_show (GTK_WIDGET (priv->individual_view));
 	gtk_container_add (GTK_CONTAINER (sw),
-			   GTK_WIDGET (priv->list_view));
-	g_signal_connect (priv->list_view, "row-activated",
+			   GTK_WIDGET (priv->individual_view));
+	g_signal_connect (priv->individual_view, "row-activated",
 			  G_CALLBACK (main_window_row_activated_cb),
 			  window);
 
 	/* Set up search bar */
 	priv->search_bar = empathy_live_search_new (
-		GTK_WIDGET (priv->list_view));
-	empathy_contact_list_view_set_live_search (priv->list_view,
+		GTK_WIDGET (priv->individual_view));
+	empathy_individual_view_set_live_search (priv->individual_view,
 		EMPATHY_LIVE_SEARCH (priv->search_bar));
 	gtk_box_pack_start (GTK_BOX (priv->main_vbox), priv->search_bar,
 		FALSE, TRUE, 0);
 	g_signal_connect_swapped (window, "map",
-		G_CALLBACK (gtk_widget_grab_focus), priv->list_view);
+		G_CALLBACK (gtk_widget_grab_focus), priv->individual_view);
 
 	/* Load user-defined accelerators. */
 	main_window_accels_load ();
@@ -1631,13 +1659,11 @@ empathy_main_window_init (EmpathyMainWindow *window)
 
 	/* Enable event handling */
 	priv->event_manager = empathy_event_manager_dup_singleton ();
+
 	g_signal_connect (priv->event_manager, "event-added",
-			  G_CALLBACK (main_window_event_added_cb),
-			  window);
+			  G_CALLBACK (main_window_event_added_cb), window);
 	g_signal_connect (priv->event_manager, "event-removed",
-			  G_CALLBACK (main_window_event_removed_cb),
-			  window);
-
+			  G_CALLBACK (main_window_event_removed_cb), window);
 	g_signal_connect (priv->account_manager, "account-validity-changed",
 			  G_CALLBACK (main_window_account_validity_changed_cb),
 			  window);
@@ -1650,8 +1676,8 @@ empathy_main_window_init (EmpathyMainWindow *window)
 
 	l = empathy_event_manager_get_events (priv->event_manager);
 	while (l) {
-		main_window_event_added_cb (priv->event_manager,
-					    l->data, window);
+		main_window_event_added_cb (priv->event_manager, l->data,
+				window);
 		l = l->next;
 	}
 
diff --git a/src/empathy-preferences.c b/src/empathy-preferences.c
index e45e540..ccc92d3 100644
--- a/src/empathy-preferences.c
+++ b/src/empathy-preferences.c
@@ -49,7 +49,7 @@
 
 G_DEFINE_TYPE (EmpathyPreferences, empathy_preferences, GTK_TYPE_DIALOG);
 
-#define GET_PRIV(self) ((EmpathyPreferencesPriv*)((EmpathyPreferences*)self)->priv)
+#define GET_PRIV(self) ((EmpathyPreferencesPriv *) ((EmpathyPreferences *) self)->priv)
 
 struct _EmpathyPreferencesPriv {
 	GtkWidget *notebook;
