]> git.0d.be Git - empathy.git/blobdiff - libempathy-gtk/gossip-contact-list.c
EmpathyContactList is now an interface implemented by
[empathy.git] / libempathy-gtk / gossip-contact-list.c
index 3a49d12d0bfed0813f2164c8377577e0dabd93e9..e4abccc80ac1bc46e914b5da64e60fe8137fd71f 100644 (file)
 #include <glade/glade.h>
 
 #include <libmissioncontrol/mc-account.h>
+#include <libmissioncontrol/mission-control.h>
 
+#include <libempathy/empathy-contact-list.h>
 #include <libempathy/empathy-contact-manager.h>
 #include <libempathy/gossip-debug.h>
-#include <libempathy/empathy-session.h>
+#include <libempathy/gossip-utils.h>
 
+#include "empathy-images.h"
 #include "gossip-contact-list.h"
 #include "gossip-contact-groups.h"
 #include "gossip-cell-renderer-expander.h"
 #include "gossip-cell-renderer-text.h"
-#include "gossip-stock.h"
 #include "gossip-ui-utils.h"
 //#include "gossip-chat-invite.h"
 //#include "gossip-contact-info-dialog.h"
 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
 
 struct _GossipContactListPriv {
-       EmpathyContactManager *manager;
+       EmpathyContactList    *list;
 
        GHashTable            *groups;
 
        GtkUIManager          *ui;
        GtkTreeRowReference   *drag_row;
 
+       GtkTreeStore          *store;
+       GtkTreeModel          *filter;
+       gchar                 *filter_text;
+
        gboolean               show_offline;
        gboolean               show_avatars;
        gboolean               is_compact;
        gboolean               show_active;
+
+       GossipContactListSort  sort_criterium;
 };
 
 typedef struct {
+       GtkTreeIter  iter;
        const gchar *name;
        gboolean     found;
-       GtkTreeIter  iter;
 } FindGroup;
 
 typedef struct {
@@ -113,9 +121,12 @@ static void     contact_list_set_property                    (GObject
                                                              guint                   param_id,
                                                              const GValue           *value,
                                                              GParamSpec             *pspec);
+static gboolean contact_list_row_separator_func              (GtkTreeModel           *model,
+                                                             GtkTreeIter            *iter,
+                                                             gpointer                data);
 static void     contact_list_contact_update                  (GossipContactList      *list,
                                                              GossipContact          *contact);
-static void     contact_list_contact_added_cb                (EmpathyContactManager  *manager,
+static void     contact_list_contact_added_cb                (EmpathyContactList     *list_iface,
                                                              GossipContact          *contact,
                                                              GossipContactList      *list);
 static void     contact_list_contact_updated_cb              (GossipContact          *contact,
@@ -124,7 +135,7 @@ static void     contact_list_contact_updated_cb              (GossipContact
 static void     contact_list_contact_groups_updated_cb       (GossipContact          *contact,
                                                              GParamSpec             *param,
                                                              GossipContactList      *list);
-static void     contact_list_contact_removed_cb              (EmpathyContactManager  *manager,
+static void     contact_list_contact_removed_cb              (EmpathyContactList     *list_iface,
                                                              GossipContact          *contact,
                                                              GossipContactList      *list);
 static void     contact_list_contact_set_active              (GossipContactList      *list,
@@ -142,7 +153,8 @@ static gchar *  contact_list_get_parent_group                (GtkTreeModel
                                                              gboolean               *path_is_group);
 static void     contact_list_get_group                       (GossipContactList      *list,
                                                              const gchar            *name,
-                                                             GtkTreeIter            *iter_to_set,
+                                                             GtkTreeIter            *iter_group_to_set,
+                                                             GtkTreeIter            *iter_separator_to_set,
                                                              gboolean               *created);
 static gboolean contact_list_get_group_foreach               (GtkTreeModel           *model,
                                                              GtkTreePath            *path,
@@ -224,10 +236,17 @@ static void     contact_list_row_expand_or_collapse_cb       (GossipContactList
                                                              GtkTreeIter            *iter,
                                                              GtkTreePath            *path,
                                                              gpointer                user_data);
-static gint     contact_list_sort_func                       (GtkTreeModel           *model,
+static gint     contact_list_name_sort_func                  (GtkTreeModel           *model,
                                                              GtkTreeIter            *iter_a,
                                                              GtkTreeIter            *iter_b,
                                                              gpointer                user_data);
+static gint     contact_list_state_sort_func                 (GtkTreeModel           *model,
+                                                             GtkTreeIter            *iter_a,
+                                                             GtkTreeIter            *iter_b,
+                                                             gpointer                user_data);
+static gboolean contact_list_filter_func                     (GtkTreeModel           *model,
+                                                             GtkTreeIter            *iter,
+                                                             GossipContactList      *list);
 static GList *  contact_list_find_contact                    (GossipContactList      *list,
                                                              GossipContact          *contact);
 static gboolean contact_list_find_contact_foreach            (GtkTreeModel           *model,
@@ -236,26 +255,15 @@ static gboolean contact_list_find_contact_foreach            (GtkTreeModel
                                                              FindContact            *fc);
 static void     contact_list_action_cb                       (GtkAction              *action,
                                                              GossipContactList      *list);
+static void     contact_list_action_activated                (GossipContactList      *list,
+                                                             GossipContact          *contact);
 static gboolean contact_list_update_list_mode_foreach        (GtkTreeModel           *model,
                                                              GtkTreePath            *path,
                                                              GtkTreeIter            *iter,
                                                              GossipContactList      *list);
-enum {
-       CONTACT_CHAT,
-       CONTACT_INFORMATION,
-       CONTACT_EDIT,
-       CONTACT_REMOVE,
-       CONTACT_INVITE,
-       CONTACT_SEND_FILE,
-       CONTACT_LOG,
-       GROUP_RENAME,
-       LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL];
 
 enum {
-       COL_PIXBUF_STATUS,
+       COL_ICON_STATUS,
        COL_PIXBUF_AVATAR,
        COL_PIXBUF_AVATAR_VISIBLE,
        COL_NAME,
@@ -265,6 +273,7 @@ enum {
        COL_IS_GROUP,
        COL_IS_ACTIVE,
        COL_IS_ONLINE,
+       COL_IS_SEPARATOR,
        COL_COUNT
 };
 
@@ -273,6 +282,8 @@ enum {
        PROP_SHOW_OFFLINE,
        PROP_SHOW_AVATARS,
        PROP_IS_COMPACT,
+       PROP_FILTER,
+       PROP_SORT_CRITERIUM
 };
 
 static const GtkActionEntry entries[] = {
@@ -284,11 +295,11 @@ static const GtkActionEntry entries[] = {
          N_("_Group"),NULL, NULL,
          NULL
        },
-       { "Chat", GOSSIP_STOCK_MESSAGE,
+       { "Chat", EMPATHY_IMAGE_MESSAGE,
          N_("_Chat"), NULL, N_("Chat with contact"),
          G_CALLBACK (contact_list_action_cb)
        },
-       { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
+       { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
          N_("Infor_mation"), "<control>I", N_("View contact information"),
          G_CALLBACK (contact_list_action_cb)
        },
@@ -304,7 +315,7 @@ static const GtkActionEntry entries[] = {
          N_("_Remove"), NULL, N_("Remove contact"),
          G_CALLBACK (contact_list_action_cb)
        },
-       { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
+       { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
          N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
          G_CALLBACK (contact_list_action_cb)
        },
@@ -359,6 +370,28 @@ static const GtkTargetEntry drag_types_source[] = {
 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
 
+GType
+gossip_contact_list_sort_get_type (void)
+{
+       static GType etype = 0;
+
+       if (etype == 0) {
+               static const GEnumValue values[] = {
+                       { GOSSIP_CONTACT_LIST_SORT_NAME, 
+                         "GOSSIP_CONTACT_LIST_SORT_NAME", 
+                         "name" },
+                       { GOSSIP_CONTACT_LIST_SORT_STATE, 
+                         "GOSSIP_CONTACT_LIST_SORT_STATE", 
+                         "state" },
+                       { 0, NULL, NULL }
+               };
+
+               etype = g_enum_register_static ("GossipContactListSort", values);
+       }
+
+       return etype;
+}
+
 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
 
 static void
@@ -370,80 +403,6 @@ gossip_contact_list_class_init (GossipContactListClass *klass)
        object_class->get_property = contact_list_get_property;
        object_class->set_property = contact_list_set_property;
 
-       signals[CONTACT_CHAT] =
-               g_signal_new ("contact-chat",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_INFORMATION] =
-               g_signal_new ("contact-information",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_EDIT] =
-               g_signal_new ("contact-edit",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_REMOVE] =
-               g_signal_new ("contact-remove",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_INVITE] =
-               g_signal_new ("contact-invite",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_SEND_FILE] =
-               g_signal_new ("contact-send-file",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[CONTACT_LOG] =
-               g_signal_new ("contact-log",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__OBJECT,
-                             G_TYPE_NONE,
-                             1, GOSSIP_TYPE_CONTACT);
-       signals[GROUP_RENAME] =
-               g_signal_new ("group-rename",
-                             G_TYPE_FROM_CLASS (klass),
-                             G_SIGNAL_RUN_LAST,
-                             0,
-                             NULL, NULL,
-                             g_cclosure_marshal_VOID__STRING,
-                             G_TYPE_NONE,
-                             1, G_TYPE_STRING);
-
-
        g_object_class_install_property (object_class,
                                         PROP_SHOW_OFFLINE,
                                         g_param_spec_boolean ("show-offline",
@@ -468,6 +427,23 @@ gossip_contact_list_class_init (GossipContactListClass *klass)
                                                               FALSE,
                                                               G_PARAM_READWRITE));
 
+       g_object_class_install_property (object_class,
+                                        PROP_FILTER,
+                                        g_param_spec_string ("filter",
+                                                             "Filter",
+                                                             "The text to use to filter the contact list",
+                                                             NULL,
+                                                             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",
+                                                            GOSSIP_TYPE_CONTACT_LIST_SORT,
+                                                            GOSSIP_CONTACT_LIST_SORT_NAME,
+                                                            G_PARAM_READWRITE));
+
        g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
 }
 
@@ -481,15 +457,14 @@ gossip_contact_list_init (GossipContactList *list)
 
        priv = GET_PRIV (list);
 
-       priv->manager = empathy_session_get_contact_manager ();
-       g_object_ref (priv->manager);
+       priv->list = EMPATHY_CONTACT_LIST (empathy_contact_manager_new ());
        priv->is_compact = FALSE;
        priv->show_active = TRUE;
        priv->show_avatars = TRUE;
 
        contact_list_create_model (list);
        contact_list_setup_view (list);
-       empathy_contact_manager_setup (priv->manager);
+       empathy_contact_list_setup (priv->list);
 
        /* Get saved group states. */
        gossip_contact_groups_get_all ();
@@ -509,12 +484,16 @@ gossip_contact_list_init (GossipContactList *list)
 
        g_object_unref (action_group);
 
+       gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list), 
+                                             contact_list_row_separator_func,
+                                             NULL, NULL);
+
        /* Signal connection. */
-       g_signal_connect (priv->manager,
+       g_signal_connect (priv->list,
                          "contact-added",
                          G_CALLBACK (contact_list_contact_added_cb),
                          list);
-       g_signal_connect (priv->manager,
+       g_signal_connect (priv->list,
                          "contact-removed",
                          G_CALLBACK (contact_list_contact_removed_cb),
                          list);
@@ -538,16 +517,17 @@ gossip_contact_list_init (GossipContactList *list)
                          GINT_TO_POINTER (FALSE));
 
        /* Add contacts already created */
-       contacts = empathy_contact_manager_get_contacts (priv->manager);
+       contacts = empathy_contact_list_get_contacts (priv->list);
        for (l = contacts; l; l = l->next) {
                GossipContact *contact;
 
                contact = l->data;
 
-               contact_list_contact_added_cb (priv->manager, contact, list);
+               contact_list_contact_added_cb (priv->list, contact, list);
 
                g_object_unref (contact);
        }
+       g_list_free (contacts);
 }
 
 static void
@@ -557,10 +537,13 @@ contact_list_finalize (GObject *object)
 
        priv = GET_PRIV (object);
 
-       /* FIXME: disconnect all signals on the manager and contacts */
+       /* FIXME: disconnect all signals on the list and contacts */
 
-       g_object_unref (priv->manager);
+       g_object_unref (priv->list);
        g_object_unref (priv->ui);
+       g_object_unref (priv->store);
+       g_object_unref (priv->filter);
+       g_free (priv->filter_text);
 
        G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
 }
@@ -585,6 +568,12 @@ contact_list_get_property (GObject    *object,
        case PROP_IS_COMPACT:
                g_value_set_boolean (value, priv->is_compact);
                break;
+       case PROP_FILTER:
+               g_value_set_string (value, priv->filter_text);
+               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;
@@ -614,12 +603,34 @@ contact_list_set_property (GObject      *object,
                gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
                                                    g_value_get_boolean (value));
                break;
+       case PROP_FILTER:
+               gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
+                                               g_value_get_string (value));
+               break;
+       case PROP_SORT_CRITERIUM:
+               gossip_contact_list_set_sort_criterium (GOSSIP_CONTACT_LIST (object),
+                                                       g_value_get_enum (value));
+               break;
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
                break;
        };
 }
 
+static gboolean
+contact_list_row_separator_func (GtkTreeModel *model,
+                                GtkTreeIter  *iter,
+                                gpointer      data)
+{
+       gboolean is_separator = FALSE;
+
+       gtk_tree_model_get (model, iter,
+                           COL_IS_SEPARATOR, &is_separator,
+                           -1);
+
+       return is_separator;
+}
+
 static void
 contact_list_contact_update (GossipContactList *list,
                             GossipContact     *contact)
@@ -636,12 +647,11 @@ contact_list_contact_update (GossipContactList *list,
        gboolean               do_remove = FALSE;
        gboolean               do_set_active = FALSE;
        gboolean               do_set_refresh = FALSE;
-       GdkPixbuf             *pixbuf_presence;
        GdkPixbuf             *pixbuf_avatar;
 
        priv = GET_PRIV (list);
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
 
        iters = contact_list_find_contact (list, contact);
        if (!iters) {
@@ -705,10 +715,7 @@ contact_list_contact_update (GossipContactList *list,
 
                /* Get online state before. */
                if (iters && g_list_length (iters) > 0) {
-                       GtkTreeIter *iter;
-
-                       iter = g_list_nth_data (iters, 0);
-                       gtk_tree_model_get (model, iter, COL_IS_ONLINE, &was_online, -1);
+                       gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
                }
 
                /* Is this really an update or an online/offline. */
@@ -738,11 +745,10 @@ contact_list_contact_update (GossipContactList *list,
                set_model = TRUE;
        }
 
-       pixbuf_presence = gossip_pixbuf_for_contact (contact);
        pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
        for (l = iters; l && set_model; l = l->next) {
-               gtk_tree_store_set (GTK_TREE_STORE (model), l->data,
-                                   COL_PIXBUF_STATUS, pixbuf_presence,
+               gtk_tree_store_set (priv->store, l->data,
+                                   COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
                                    COL_STATUS, gossip_contact_get_status (contact),
                                    COL_IS_ONLINE, now_online,
                                    COL_NAME, gossip_contact_get_name (contact),
@@ -750,9 +756,6 @@ contact_list_contact_update (GossipContactList *list,
                                    -1);
        }
 
-       if (pixbuf_presence) {
-               g_object_unref (pixbuf_presence);
-       }
        if (pixbuf_avatar) {
                g_object_unref (pixbuf_avatar);
        }
@@ -778,18 +781,18 @@ contact_list_contact_update (GossipContactList *list,
 }
 
 static void
-contact_list_contact_added_cb (EmpathyContactManager *manager,
-                              GossipContact         *contact,
-                              GossipContactList     *list)
+contact_list_contact_added_cb (EmpathyContactList *list_iface,
+                              GossipContact      *contact,
+                              GossipContactList  *list)
 {
        GossipContactListPriv *priv;
 
        priv = GET_PRIV (list);
 
-       gossip_debug (DEBUG_DOMAIN, "Contact:'%s' added",
+       gossip_debug (DEBUG_DOMAIN, 
+                     "Contact:'%s' added",
                      gossip_contact_get_name (contact));
 
-       /* Connect notifications for contact updates */
        g_signal_connect (contact, "notify::groups",
                          G_CALLBACK (contact_list_contact_groups_updated_cb),
                          list);
@@ -838,13 +841,17 @@ contact_list_contact_updated_cb (GossipContact     *contact,
                                 GParamSpec        *param,
                                 GossipContactList *list)
 {
+       gossip_debug (DEBUG_DOMAIN,
+                     "Contact:'%s' updated, checking roster is in sync...",
+                     gossip_contact_get_name (contact));
+
        contact_list_contact_update (list, contact);
 }
 
 static void
-contact_list_contact_removed_cb (EmpathyContactManager *manager,
-                                GossipContact         *contact,
-                                GossipContactList     *list)
+contact_list_contact_removed_cb (EmpathyContactList *list_iface,
+                                GossipContact      *contact,
+                                GossipContactList  *list)
 {
        gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
                      gossip_contact_get_name (contact));
@@ -866,32 +873,34 @@ contact_list_contact_set_active (GossipContactList *list,
                                 gboolean           active,
                                 gboolean           set_changed)
 {
-       GtkTreeModel *model;
-       GList        *iters, *l;
+       GossipContactListPriv *priv;
+       GtkTreeModel          *model;
+       GList                 *iters, *l;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       priv = GET_PRIV (list);
+
+       model = GTK_TREE_MODEL (priv->store);
 
        iters = contact_list_find_contact (list, contact);
        for (l = iters; l; l = l->next) {
                GtkTreePath *path;
-               GtkTreeIter *iter;
-
-               iter = l->data;
 
-               gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+               gtk_tree_store_set (priv->store, l->data,
                                    COL_IS_ACTIVE, active,
                                    -1);
+
                gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
 
                if (set_changed) {
-                       path = gtk_tree_model_get_path (model, iter);
-                       gtk_tree_model_row_changed (model, path, iter);
+                       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 *
@@ -1008,20 +1017,54 @@ contact_list_get_parent_group (GtkTreeModel *model,
        return name;
 }
 
+static gboolean
+contact_list_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,
+                           COL_NAME, &str,
+                           COL_IS_GROUP, &is_group,
+                           -1);
+
+       if (is_group && strcmp (str, fg->name) == 0) {
+               fg->found = TRUE;
+               fg->iter = *iter;
+       }
+
+       g_free (str);
+
+       return fg->found;
+}
+
 static void
 contact_list_get_group (GossipContactList *list,
                        const gchar       *name,
-                       GtkTreeIter       *iter_to_set,
+                       GtkTreeIter       *iter_group_to_set,
+                       GtkTreeIter       *iter_separator_to_set,
                        gboolean          *created)
 {
-       GtkTreeModel *model;
-       FindGroup     fg;
+       GossipContactListPriv *priv;
+       GtkTreeModel          *model;
+       GtkTreeIter            iter_group, iter_separator;
+       FindGroup              fg;
+
+       priv = GET_PRIV (list);
 
        memset (&fg, 0, sizeof (fg));
 
        fg.name = name;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
        gtk_tree_model_foreach (model,
                                (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
                                &fg);
@@ -1031,48 +1074,52 @@ contact_list_get_group (GossipContactList *list,
                        *created = TRUE;
                }
 
-               gtk_tree_store_append (GTK_TREE_STORE (model), iter_to_set, NULL);
-               gtk_tree_store_set (GTK_TREE_STORE (model), iter_to_set,
-                                   COL_PIXBUF_STATUS, NULL,
+               gtk_tree_store_append (priv->store, &iter_group, NULL);
+               gtk_tree_store_set (priv->store, &iter_group,
+                                   COL_ICON_STATUS, NULL,
                                    COL_NAME, name,
                                    COL_IS_GROUP, TRUE,
                                    COL_IS_ACTIVE, FALSE,
+                                   COL_IS_SEPARATOR, FALSE,
+                                   -1);
+
+               if (iter_group_to_set) {
+                       *iter_group_to_set = iter_group;
+               }
+
+               gtk_tree_store_append (priv->store,
+                                      &iter_separator, 
+                                      &iter_group);
+               gtk_tree_store_set (priv->store, &iter_separator,
+                                   COL_IS_SEPARATOR, TRUE,
                                    -1);
+
+               if (iter_separator_to_set) {
+                       *iter_separator_to_set = iter_separator;
+               }
        } else {
                if (created) {
                        *created = FALSE;
                }
 
-               *iter_to_set = fg.iter;
-       }
-}
-
-static gboolean
-contact_list_get_group_foreach (GtkTreeModel *model,
-                               GtkTreePath  *path,
-                               GtkTreeIter  *iter,
-                               FindGroup    *fg)
-{
-       gchar    *str;
-       gboolean  is_group;
+               if (iter_group_to_set) {
+                       *iter_group_to_set = fg.iter;
+               }
 
-       /* Groups are only at the top level. */
-       if (gtk_tree_path_get_depth (path) != 1) {
-               return FALSE;
-       }
+               iter_separator = fg.iter;
 
-       gtk_tree_model_get (model, iter,
-                           COL_NAME, &str,
-                           COL_IS_GROUP, &is_group,
-                           -1);
-       if (is_group && strcmp (str, fg->name) == 0) {
-               fg->found = TRUE;
-               fg->iter = *iter;
-       }
+               if (gtk_tree_model_iter_next (model, &iter_separator)) {
+                       gboolean is_separator;
 
-       g_free (str);
+                       gtk_tree_model_get (model, &iter_separator,
+                                           COL_IS_SEPARATOR, &is_separator,
+                                           -1);
 
-       return fg->found;
+                       if (is_separator && iter_separator_to_set) {
+                               *iter_separator_to_set = iter_separator;
+                       }
+               }
+       }
 }
 
 static void
@@ -1080,7 +1127,7 @@ contact_list_add_contact (GossipContactList *list,
                          GossipContact     *contact)
 {
        GossipContactListPriv *priv;
-       GtkTreeIter            iter, iter_group;
+       GtkTreeIter            iter, iter_group, iter_separator;
        GtkTreeModel          *model;
        GList                 *l, *groups;
 
@@ -1095,21 +1142,28 @@ contact_list_add_contact (GossipContactList *list,
        /* If no groups just add it at the top level. */
        groups = gossip_contact_get_groups (contact);
        if (!groups) {
-               GdkPixbuf *pixbuf_status;
                GdkPixbuf *pixbuf_avatar;
                gboolean   show_avatar = FALSE;
 
-               pixbuf_status = gossip_pixbuf_for_contact (contact);
-               pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
-                       contact, 32, 32);
+               pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
 
                if (priv->show_avatars && !priv->is_compact) {
                        show_avatar = TRUE;
                }
 
-               gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
-               gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
-                                   COL_PIXBUF_STATUS, pixbuf_status,
+               gossip_debug (DEBUG_DOMAIN, "");
+               gossip_debug (DEBUG_DOMAIN, 
+                             "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
+
+               gossip_debug (DEBUG_DOMAIN, 
+                             "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
+                             contact,
+                             G_IS_OBJECT (contact) ? "yes" : "no",
+                             GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
+
+               gtk_tree_store_append (priv->store, &iter, NULL);
+               gtk_tree_store_set (priv->store, &iter,
+                                   COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
                                    COL_PIXBUF_AVATAR, pixbuf_avatar,
                                    COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
                                    COL_NAME, gossip_contact_get_name (contact),
@@ -1119,23 +1173,26 @@ contact_list_add_contact (GossipContactList *list,
                                    COL_IS_GROUP, FALSE,
                                    COL_IS_ACTIVE, FALSE,
                                    COL_IS_ONLINE, gossip_contact_is_online (contact),
+                                   COL_IS_SEPARATOR, FALSE,
                                    -1);
 
+               gossip_debug (DEBUG_DOMAIN, 
+                             "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
+               gossip_debug (DEBUG_DOMAIN, "");
+
                if (pixbuf_avatar) {
                        g_object_unref (pixbuf_avatar);
                }
-               if (pixbuf_status) {
-                       g_object_unref (pixbuf_status);
-               }
        }
 
        /* Else add to each group. */
        for (l = groups; l; l = l->next) {
                GtkTreePath *path;
-               GdkPixbuf   *pixbuf_status;
+               GtkTreeIter  model_iter_group;
                GdkPixbuf   *pixbuf_avatar;
                const gchar *name;
                gboolean     created;
+               gboolean     found;
                gboolean     show_avatar = FALSE;
 
                name = l->data;
@@ -1143,19 +1200,27 @@ contact_list_add_contact (GossipContactList *list,
                        continue;
                }
 
-               pixbuf_status = gossip_pixbuf_for_contact (contact);
-               pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
-                       contact, 32, 32);
+               pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
 
-               contact_list_get_group (list, name, &iter_group, &created);
+               contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
 
                if (priv->show_avatars && !priv->is_compact) {
                        show_avatar = TRUE;
                }
 
-               gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &iter_group);
-               gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
-                                   COL_PIXBUF_STATUS, pixbuf_status,
+               gossip_debug (DEBUG_DOMAIN, "");
+               gossip_debug (DEBUG_DOMAIN, 
+                             "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
+
+               gossip_debug (DEBUG_DOMAIN, 
+                             "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
+                             contact,
+                             G_IS_OBJECT (contact) ? "yes" : "no",
+                             GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
+
+               gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
+               gtk_tree_store_set (priv->store, &iter,
+                                   COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
                                    COL_PIXBUF_AVATAR, pixbuf_avatar,
                                    COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
                                    COL_NAME, gossip_contact_get_name (contact),
@@ -1165,20 +1230,29 @@ contact_list_add_contact (GossipContactList *list,
                                    COL_IS_GROUP, FALSE,
                                    COL_IS_ACTIVE, FALSE,
                                    COL_IS_ONLINE, gossip_contact_is_online (contact),
+                                   COL_IS_SEPARATOR, FALSE,
                                    -1);
 
+               gossip_debug (DEBUG_DOMAIN, 
+                             "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
+               gossip_debug (DEBUG_DOMAIN, "");
+
                if (pixbuf_avatar) {
                        g_object_unref (pixbuf_avatar);
                }
-               if (pixbuf_status) {
-                       g_object_unref (pixbuf_status);
-               }
 
                if (!created) {
                        continue;
                }
 
-               path = gtk_tree_model_get_path (model, &iter_group);
+               found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),  
+                                                                         &model_iter_group,  
+                                                                         &iter_group); 
+               if (!found) {
+                       continue;
+               }
+               
+               path = gtk_tree_model_get_path (model, &model_iter_group);
                if (!path) {
                        continue;
                }
@@ -1221,53 +1295,80 @@ contact_list_remove_contact (GossipContactList *list,
        }
        
        /* Clean up model */
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
 
        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) <= 1) {
-                       gtk_tree_store_remove (GTK_TREE_STORE (model), &parent);
+                   gtk_tree_model_iter_n_children (model, &parent) <= 2) {
+                       gtk_tree_store_remove (priv->store, &parent);
                } else {
-                       gtk_tree_store_remove (GTK_TREE_STORE (model), l->data);
+                       gtk_tree_store_remove (priv->store, l->data);
                }
        }
 
-       g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
+       g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
        g_list_free (iters);
 }
 
 static void
 contact_list_create_model (GossipContactList *list)
 {
-       GtkTreeModel *model;
+       GossipContactListPriv *priv;
+       GtkTreeModel          *model;
+       
+       priv = GET_PRIV (list);
+
+       if (priv->store) {
+               g_object_unref (priv->store);
+       }
+
+       if (priv->filter) {
+               g_object_unref (priv->filter);
+       }
 
-       model = GTK_TREE_MODEL (
-               gtk_tree_store_new (COL_COUNT,
-                                   GDK_TYPE_PIXBUF,     /* Status pixbuf */
-                                   GDK_TYPE_PIXBUF,     /* Avatar pixbuf */
-                                   G_TYPE_BOOLEAN,      /* Avatar pixbuf visible */
-                                   G_TYPE_STRING,       /* Name */
-                                   G_TYPE_STRING,       /* Status string */
-                                   G_TYPE_BOOLEAN,      /* Show status */
-                                   GOSSIP_TYPE_CONTACT, /* Contact type */
-                                   G_TYPE_BOOLEAN,      /* Is group */
-                                   G_TYPE_BOOLEAN,      /* Is active */
-                                   G_TYPE_BOOLEAN));      /* Is online */
+       priv->store = gtk_tree_store_new (COL_COUNT,
+                                         G_TYPE_STRING,       /* Status icon-name */
+                                         GDK_TYPE_PIXBUF,     /* Avatar pixbuf */
+                                         G_TYPE_BOOLEAN,      /* Avatar pixbuf visible */
+                                         G_TYPE_STRING,       /* Name */
+                                         G_TYPE_STRING,       /* Status string */
+                                         G_TYPE_BOOLEAN,      /* Show status */
+                                         GOSSIP_TYPE_CONTACT, /* Contact type */
+                                         G_TYPE_BOOLEAN,      /* Is group */
+                                         G_TYPE_BOOLEAN,      /* Is active */
+                                         G_TYPE_BOOLEAN,      /* Is online */
+                                         G_TYPE_BOOLEAN);     /* Is separator */
 
+       /* Save normal model */
+       model = GTK_TREE_MODEL (priv->store);
+
+       /* Set up sorting */
        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
                                         COL_NAME,
-                                        contact_list_sort_func,
+                                        contact_list_name_sort_func,
+                                        list, NULL);
+       gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
+                                        COL_STATUS,
+                                        contact_list_state_sort_func,
                                         list, NULL);
 
-       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
-                                             COL_NAME,
-                                             GTK_SORT_ASCENDING);
+       gossip_contact_list_set_sort_criterium (list, priv->sort_criterium);
+
+       /* Create filter */
+       priv->filter = gtk_tree_model_filter_new (model, NULL);
 
-       gtk_tree_view_set_model (GTK_TREE_VIEW (list), model);
+       gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
+                                               (GtkTreeModelFilterVisibleFunc)
+                                               contact_list_filter_func,
+                                               list, NULL);
 
-       g_object_unref (model);
+       gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
 }
 
 static gboolean
@@ -1281,14 +1382,21 @@ contact_list_search_equal_func (GtkTreeModel *model,
        gchar    *key_folded;
        gboolean  ret;
 
-       gtk_tree_model_get (model, iter,
-                           COL_NAME, &name,
-                           -1);
+       if (!key) {
+               return FALSE;
+       }
+
+       gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
+
+       if (!name) {
+               return FALSE;
+       }
 
        name_folded = g_utf8_casefold (name, -1);
        key_folded = g_utf8_casefold (key, -1);
 
-       if (strstr (name_folded, key_folded)) {
+       if (name_folded && key_folded && 
+           strstr (name_folded, key_folded)) {
                ret = FALSE;
        } else {
                ret = TRUE;
@@ -1462,7 +1570,7 @@ contact_list_drag_data_received (GtkWidget         *widget,
                      id);
 
        /* FIXME: This is ambigous, an id can come from multiple accounts */
-       contact = empathy_contact_manager_find (priv->manager, id);
+       contact = empathy_contact_list_find (priv->list, id);
        if (!contact) {
                gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
                return;
@@ -1730,7 +1838,6 @@ contact_list_cell_set_background (GossipContactList  *list,
 {
        GdkColor  color;
        GtkStyle *style;
-       gint color_sum_normal, color_sum_selected;
 
        g_return_if_fail (list != NULL);
        g_return_if_fail (cell != NULL);
@@ -1759,19 +1866,23 @@ contact_list_cell_set_background (GossipContactList  *list,
                                      NULL);
                }
        } else {
+#if 0
+               gint color_sum_normal;
+               gint color_sum_selected;
+               
                color = style->base[GTK_STATE_SELECTED];
                color_sum_normal = color.red+color.green+color.blue;
                color = style->base[GTK_STATE_NORMAL];
                color_sum_selected = color.red+color.green+color.blue;
                color = style->text_aa[GTK_STATE_INSENSITIVE];
 
-               if(color_sum_normal < color_sum_selected) { 
-                       /* found a light theme */
+               if (color_sum_normal < color_sum_selected) { 
+                       /* Found a light theme */
                        color.red = (color.red + (style->white).red) / 2;
                        color.green = (color.green + (style->white).green) / 2;
                        color.blue = (color.blue + (style->white).blue) / 2;
                } else { 
-                       /* found a dark theme */
+                       /* Found a dark theme */
                        color.red = (color.red + (style->black).red) / 2;
                        color.green = (color.green + (style->black).green) / 2;
                        color.blue = (color.blue + (style->black).blue) / 2;
@@ -1780,6 +1891,7 @@ contact_list_cell_set_background (GossipContactList  *list,
                g_object_set (cell,
                              "cell-background-gdk", &color,
                              NULL);
+#endif
        }
 }
 
@@ -1790,24 +1902,22 @@ contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
                                    GtkTreeIter       *iter,
                                    GossipContactList *list)
 {
-       GdkPixbuf *pixbuf;
-       gboolean   is_group;
-       gboolean   is_active;
+       gchar    *icon_name;
+       gboolean  is_group;
+       gboolean  is_active;
 
        gtk_tree_model_get (model, iter,
                            COL_IS_GROUP, &is_group,
                            COL_IS_ACTIVE, &is_active,
-                           COL_PIXBUF_STATUS, &pixbuf,
+                           COL_ICON_STATUS, &icon_name,
                            -1);
 
        g_object_set (cell,
                      "visible", !is_group,
-                     "pixbuf", pixbuf,
+                     "icon-name", icon_name,
                      NULL);
 
-       if (pixbuf) {
-               g_object_unref (pixbuf);
-       }
+       g_free (icon_name);
 
        contact_list_cell_set_background (list, cell, is_group, is_active);
 }
@@ -1980,7 +2090,7 @@ contact_list_button_press_event_cb (GossipContactList *list,
        priv = GET_PRIV (list);
 
        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
 
        gtk_widget_grab_focus (GTK_WIDGET (list));
 
@@ -2038,7 +2148,7 @@ contact_list_row_activated_cb (GossipContactList *list,
        gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
 
        if (contact) {
-               g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+               contact_list_action_activated (list, contact);
                g_object_unref (contact);
        }
 }
@@ -2066,27 +2176,122 @@ contact_list_row_expand_or_collapse_cb (GossipContactList *list,
 }
 
 static gint
-contact_list_sort_func (GtkTreeModel *model,
-                       GtkTreeIter  *iter_a,
-                       GtkTreeIter  *iter_b,
-                       gpointer      user_data)
+contact_list_state_sort_func (GtkTreeModel *model,
+                             GtkTreeIter  *iter_a,
+                             GtkTreeIter  *iter_b,
+                             gpointer      user_data)
+{
+       gint            ret_val = 0;
+       gchar          *name_a, *name_b;
+       gboolean        is_separator_a, is_separator_b;
+       GossipContact  *contact_a, *contact_b;
+       GossipPresence *presence_a, *presence_b;
+       McPresence      state_a, state_b;
+
+       gtk_tree_model_get (model, iter_a,
+                           COL_NAME, &name_a,
+                           COL_CONTACT, &contact_a,
+                           COL_IS_SEPARATOR, &is_separator_a,
+                           -1);
+       gtk_tree_model_get (model, iter_b,
+                           COL_NAME, &name_b,
+                           COL_CONTACT, &contact_b,
+                           COL_IS_SEPARATOR, &is_separator_b,
+                           -1);
+
+       /* Separator or group? */
+       if (is_separator_a || is_separator_b) {
+               if (is_separator_a) {
+                       ret_val = -1;
+               } else if (is_separator_b) {
+                       ret_val = 1;
+               }
+       } else if (!contact_a && contact_b) {
+               ret_val = 1;
+       } else if (contact_a && !contact_b) {
+               ret_val = -1;
+       } else if (!contact_a && !contact_b) {
+               /* Handle groups */
+               ret_val = g_utf8_collate (name_a, name_b);
+       }
+
+       if (ret_val) {
+               goto free_and_out;
+       }
+
+       /* If we managed to get this far, we can start looking at
+        * the presences.
+        */
+       presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
+       presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
+
+       if (!presence_a && presence_b) {
+               ret_val = 1;
+       } else if (presence_a && !presence_b) {
+               ret_val = -1;
+       } else if (!presence_a && !presence_b) {
+               /* Both offline, sort by name */
+               ret_val = g_utf8_collate (name_a, name_b);
+       } else {
+               state_a = gossip_presence_get_state (presence_a);
+               state_b = gossip_presence_get_state (presence_b);
+
+               if (state_a < state_b) {
+                       ret_val = -1;
+               } else if (state_a > state_b) {
+                       ret_val = 1;
+               } else {
+                       /* Fallback: compare by name */
+                       ret_val = g_utf8_collate (name_a, name_b);
+               }
+       }
+
+free_and_out:
+       g_free (name_a);
+       g_free (name_b);
+
+       if (contact_a) {
+               g_object_unref (contact_a);
+       }
+
+       if (contact_b) {
+               g_object_unref (contact_b);
+       }
+
+       return ret_val;
+}
+
+static gint
+contact_list_name_sort_func (GtkTreeModel *model,
+                            GtkTreeIter  *iter_a,
+                            GtkTreeIter  *iter_b,
+                            gpointer      user_data)
 {
        gchar         *name_a, *name_b;
        GossipContact *contact_a, *contact_b;
+       gboolean       is_separator_a, is_separator_b;
        gint           ret_val;
 
        gtk_tree_model_get (model, iter_a,
                            COL_NAME, &name_a,
                            COL_CONTACT, &contact_a,
+                           COL_IS_SEPARATOR, &is_separator_a,
                            -1);
        gtk_tree_model_get (model, iter_b,
                            COL_NAME, &name_b,
                            COL_CONTACT, &contact_b,
+                           COL_IS_SEPARATOR, &is_separator_b,
                            -1);
 
        /* If contact is NULL it means it's a group. */
 
-       if (!contact_a && contact_b) {
+       if (is_separator_a || is_separator_b) {
+               if (is_separator_a) {
+                       ret_val = -1;
+               } else if (is_separator_b) {
+                       ret_val = 1;
+               }
+       } else if (!contact_a && contact_b) {
                ret_val = 1;
        } else if (contact_a && !contact_b) {
                ret_val = -1;
@@ -2108,6 +2313,121 @@ contact_list_sort_func (GtkTreeModel *model,
        return ret_val;
 }
 
+static gboolean 
+contact_list_filter_show_contact (GossipContact *contact,
+                                 const gchar   *filter)
+{
+       gchar    *str;
+       gboolean  visible;
+
+       /* Check contact id */
+       str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
+       visible = G_STR_EMPTY (str) || strstr (str, filter);
+       g_free (str);
+
+       if (visible) {
+               return TRUE;
+       }
+
+       /* Check contact name */
+       str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
+       visible = G_STR_EMPTY (str) || strstr (str, filter);
+       g_free (str);
+       
+       return visible;
+}
+
+static gboolean
+contact_list_filter_show_group (GossipContactList *list,
+                               const gchar       *group,
+                               const gchar       *filter)
+{
+       GossipContactListPriv *priv;
+       GList                 *contacts, *l;
+       gchar                 *str;
+       gboolean               show_group = FALSE;
+
+       priv = GET_PRIV (list);
+       
+       str = g_utf8_casefold (group, -1);
+       if (!str) {
+               return FALSE;
+       }
+
+       /* If the filter is the partially the group name, we show the
+        * whole group.
+        */
+       if (strstr (str, filter)) {
+               g_free (str);
+               return TRUE;
+       }
+
+       /* At this point, we need to check in advance if this
+        * group should be shown because a contact we want to
+        * show exists in it.
+        */
+       contacts = empathy_contact_list_get_contacts (priv->list);
+       for (l = contacts; l && !show_group; l = l->next) {
+               if (!gossip_contact_is_in_group (l->data, group)) {
+                       continue;
+               }
+
+               if (contact_list_filter_show_contact (l->data, filter)) {
+                       show_group = TRUE;
+               }
+       }
+       g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
+       g_list_free (contacts);
+       g_free (str);
+
+       return show_group;
+}
+
+static gboolean
+contact_list_filter_func (GtkTreeModel      *model,
+                         GtkTreeIter       *iter,
+                         GossipContactList *list)
+{
+       GossipContactListPriv *priv;
+       gboolean               is_group;
+       gboolean               is_separator;
+       gboolean               visible = TRUE;
+
+       priv = GET_PRIV (list);
+
+       if (G_STR_EMPTY (priv->filter_text)) {
+               return TRUE;
+       }
+       
+       /* Check to see if iter matches any group names */
+       gtk_tree_model_get (model, iter,
+                           COL_IS_GROUP, &is_group,
+                           COL_IS_SEPARATOR, &is_separator,
+                           -1);
+
+       if (is_group) {
+               gchar *name;
+
+               gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
+               visible &= contact_list_filter_show_group (list, 
+                                                          name, 
+                                                          priv->filter_text);
+               g_free (name);
+       } else if (is_separator) {
+               /* Do nothing here */
+       } else {
+               GossipContact *contact;
+
+               /* Check contact id */
+               gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
+               visible &= contact_list_filter_show_contact (contact, 
+                                                            priv->filter_text);
+               g_object_unref (contact);
+       }
+
+       return visible;
+}
+
 static gboolean
 contact_list_iter_equal_contact (GtkTreeModel  *model,
                                 GtkTreeIter   *iter,
@@ -2130,19 +2450,40 @@ contact_list_iter_equal_contact (GtkTreeModel  *model,
        return equal;
 }
 
+static gboolean
+contact_list_find_contact_foreach (GtkTreeModel *model,
+                                  GtkTreePath  *path,
+                                  GtkTreeIter  *iter,
+                                  FindContact  *fc)
+{
+       if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
+               fc->found = TRUE;
+               fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
+       }
+
+       /* We want to find ALL contacts that match, this means if we
+        * have the same contact in 3 groups, all iters should be
+        * returned.
+        */
+       return FALSE;
+}
+
 static GList *
 contact_list_find_contact (GossipContactList *list,
                           GossipContact     *contact)
 {
-       GtkTreeModel *model;
-       FindContact   fc;
-       GList        *l = NULL;
+       GossipContactListPriv *priv;
+       GtkTreeModel          *model;
+       GList                 *l = NULL;
+       FindContact            fc;
+
+       priv = GET_PRIV (list);
 
        memset (&fc, 0, sizeof (fc));
 
        fc.contact = contact;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
        gtk_tree_model_foreach (model,
                                (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
                                &fc);
@@ -2154,24 +2495,6 @@ contact_list_find_contact (GossipContactList *list,
        return l;
 }
 
-static gboolean
-contact_list_find_contact_foreach (GtkTreeModel *model,
-                                  GtkTreePath  *path,
-                                  GtkTreeIter  *iter,
-                                  FindContact  *fc)
-{
-       if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
-               fc->found = TRUE;
-               fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
-       }
-
-       /* We want to find ALL contacts that match, this means if we
-        * have the same contact in 3 groups, all iters should be
-        * returned.
-        */
-       return FALSE;
-}
-
 static void
 contact_list_action_cb (GtkAction         *action,
                        GossipContactList *list)
@@ -2191,28 +2514,21 @@ contact_list_action_cb (GtkAction         *action,
        group = gossip_contact_list_get_selected_group (list);
 
        if (contact && strcmp (name, "Chat") == 0) {
-               g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
+               contact_list_action_activated (list, contact);
        }
        else if (contact && strcmp (name, "Information") == 0) {
-               g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
        }
        else if (contact && strcmp (name, "Edit") == 0) {
-               g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
        }
        else if (contact && strcmp (name, "Remove") == 0) {
-               g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
        }
        else if (contact && strcmp (name, "Invite") == 0) {
-               g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
        }
        else if (contact && strcmp (name, "SendFile") == 0) {
-               g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
        }
        else if (contact && strcmp (name, "Log") == 0) {
-               g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
        }
        else if (group && strcmp (name, "Rename") == 0) {
-               g_signal_emit (list, signals[GROUP_RENAME], 0, group);
        }
 
        g_free (group);
@@ -2221,6 +2537,22 @@ contact_list_action_cb (GtkAction         *action,
        }
 }
 
+static void
+contact_list_action_activated (GossipContactList *list,
+                              GossipContact     *contact)
+{
+       MissionControl *mc;
+
+       mc = gossip_mission_control_new ();
+       mission_control_request_channel (mc,
+                                        gossip_contact_get_account (contact),
+                                        TP_IFACE_CHANNEL_TYPE_TEXT,
+                                        gossip_contact_get_handle (contact),
+                                        TP_HANDLE_TYPE_CONTACT,
+                                        NULL, NULL);
+       g_object_unref (mc);
+}
+
 static gboolean
 contact_list_update_list_mode_foreach (GtkTreeModel      *model,
                                       GtkTreePath       *path,
@@ -2236,7 +2568,7 @@ contact_list_update_list_mode_foreach (GtkTreeModel      *model,
                show_avatar = TRUE;
        }
 
-       gtk_tree_store_set (GTK_TREE_STORE (model), iter,
+       gtk_tree_store_set (priv->store, iter,
                            COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
                            COL_STATUS_VISIBLE, !priv->is_compact,
                            -1);
@@ -2317,6 +2649,42 @@ gossip_contact_list_get_show_offline (GossipContactList *list)
        return priv->show_offline;
 }
 
+gboolean
+gossip_contact_list_get_show_avatars (GossipContactList *list)
+{
+       GossipContactListPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+       priv = GET_PRIV (list);
+
+       return priv->show_avatars;
+}
+
+gboolean
+gossip_contact_list_get_is_compact (GossipContactList *list)
+{
+       GossipContactListPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+
+       priv = GET_PRIV (list);
+
+       return priv->is_compact;
+}
+
+GossipContactListSort
+gossip_contact_list_get_sort_criterium (GossipContactList *list)
+{
+       GossipContactListPriv *priv;
+
+       g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), 0);
+
+       priv = GET_PRIV (list);
+
+       return priv->sort_criterium;
+}
+
 void
 gossip_contact_list_set_show_offline (GossipContactList *list,
                                      gboolean           show_offline)
@@ -2335,7 +2703,7 @@ gossip_contact_list_set_show_offline (GossipContactList *list,
        /* Disable temporarily. */
        priv->show_active = FALSE;
 
-       contacts = empathy_contact_manager_get_contacts (priv->manager);
+       contacts = empathy_contact_list_get_contacts (priv->list);
        for (l = contacts; l; l = l->next) {
                GossipContact *contact;
 
@@ -2351,21 +2719,30 @@ gossip_contact_list_set_show_offline (GossipContactList *list,
        priv->show_active = show_active;
 }
 
-gboolean
-gossip_contact_list_get_show_avatars (GossipContactList *list)
+void
+gossip_contact_list_set_show_avatars (GossipContactList *list,
+                                     gboolean           show_avatars)
 {
        GossipContactListPriv *priv;
+       GtkTreeModel          *model;
 
-       g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+       g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
 
        priv = GET_PRIV (list);
 
-       return priv->show_avatars;
+       priv->show_avatars = show_avatars;
+
+       model = GTK_TREE_MODEL (priv->store);
+
+       gtk_tree_model_foreach (model,
+                               (GtkTreeModelForeachFunc)
+                               contact_list_update_list_mode_foreach,
+                               list);
 }
 
 void
-gossip_contact_list_set_show_avatars (GossipContactList *list,
-                                     gboolean           show_avatars)
+gossip_contact_list_set_is_compact (GossipContactList *list,
+                                   gboolean           is_compact)
 {
        GossipContactListPriv *priv;
        GtkTreeModel          *model;
@@ -2374,9 +2751,9 @@ gossip_contact_list_set_show_avatars (GossipContactList *list,
 
        priv = GET_PRIV (list);
 
-       priv->show_avatars = show_avatars;
+       priv->is_compact = is_compact;
 
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       model = GTK_TREE_MODEL (priv->store);
 
        gtk_tree_model_foreach (model,
                                (GtkTreeModelForeachFunc)
@@ -2384,36 +2761,50 @@ gossip_contact_list_set_show_avatars (GossipContactList *list,
                                list);
 }
 
-gboolean
-gossip_contact_list_get_is_compact (GossipContactList *list)
+void
+gossip_contact_list_set_sort_criterium (GossipContactList     *list,
+                                       GossipContactListSort  sort_criterium)
 {
        GossipContactListPriv *priv;
 
-       g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
+       g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
 
        priv = GET_PRIV (list);
 
-       return priv->is_compact;
+       priv->sort_criterium = sort_criterium;
+
+       switch (sort_criterium) {
+       case GOSSIP_CONTACT_LIST_SORT_STATE:
+               gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
+                                                     COL_STATUS,
+                                                     GTK_SORT_ASCENDING);
+               break;
+               
+       case GOSSIP_CONTACT_LIST_SORT_NAME:
+               gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
+                                                     COL_NAME,
+                                                     GTK_SORT_ASCENDING);
+               break;
+       }
 }
 
 void
-gossip_contact_list_set_is_compact (GossipContactList *list,
-                                   gboolean           is_compact)
+gossip_contact_list_set_filter (GossipContactList *list,
+                               const gchar       *filter)
 {
        GossipContactListPriv *priv;
-       GtkTreeModel          *model;
 
        g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
 
        priv = GET_PRIV (list);
 
-       priv->is_compact = is_compact;
-
-       model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+       g_free (priv->filter_text);
+       if (filter) {
+               priv->filter_text = g_utf8_casefold (filter, -1);
+       } else {
+               priv->filter_text = NULL;
+       }
 
-       gtk_tree_model_foreach (model,
-                               (GtkTreeModelForeachFunc)
-                               contact_list_update_list_mode_foreach,
-                               list);
+       gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
+       gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
 }
-