/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2005-2007 Imendio AB
* Copyright (C) 2007-2008 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>
*/
#include "config.h"
#include <string.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <telepathy-glib/util.h>
#include <libempathy/empathy-utils.h>
#include <libempathy/empathy-tp-chat.h>
#include <libempathy/empathy-enum-types.h>
#include <libempathy/empathy-contact-manager.h>
#include "empathy-contact-list-store.h"
#include "empathy-ui-utils.h"
#include "empathy-gtk-enum-types.h"
#include "empathy-images.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, EmpathyContactListStore)
typedef struct {
EmpathyContactList *list;
gboolean show_offline;
gboolean show_avatars;
gboolean show_groups;
gboolean is_compact;
gboolean show_protocols;
gboolean show_active;
EmpathyContactListStoreSort sort_criterium;
guint inhibit_active;
guint setup_idle_id;
gboolean dispose_has_run;
GHashTable *status_icons;
} EmpathyContactListStorePriv;
typedef struct {
GtkTreeIter iter;
const gchar *name;
gboolean found;
} FindGroup;
typedef struct {
EmpathyContact *contact;
gboolean found;
GList *iters;
} FindContact;
typedef struct {
EmpathyContactListStore *store;
EmpathyContact *contact;
gboolean remove;
} ShowActiveData;
static void contact_list_store_dispose (GObject *object);
static void contact_list_store_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec);
static void contact_list_store_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec);
static void contact_list_store_setup (EmpathyContactListStore *store);
static gboolean contact_list_store_inibit_active_cb (EmpathyContactListStore *store);
static void contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
EmpathyContact *actor,
guint reason,
gchar *message,
gboolean is_member,
EmpathyContactListStore *store);
static void contact_list_store_favourites_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
gboolean is_favourite,
EmpathyContactListStore *store);
static void contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
EmpathyContact *old_contact,
EmpathyContact *new_contact,
guint reason,
gchar *message,
EmpathyContactListStore *store);
static void contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
gchar *group,
gboolean is_member,
EmpathyContactListStore *store);
static void contact_list_store_add_contact (EmpathyContactListStore *store,
EmpathyContact *contact);
static void contact_list_store_remove_contact (EmpathyContactListStore *store,
EmpathyContact *contact);
static void contact_list_store_contact_update (EmpathyContactListStore *store,
EmpathyContact *contact);
static void contact_list_store_contact_updated_cb (EmpathyContact *contact,
GParamSpec *param,
EmpathyContactListStore *store);
static void contact_list_store_contact_set_active (EmpathyContactListStore *store,
EmpathyContact *contact,
gboolean active,
gboolean set_changed);
static ShowActiveData * contact_list_store_contact_active_new (EmpathyContactListStore *store,
EmpathyContact *contact,
gboolean remove);
static void contact_list_store_contact_active_free (ShowActiveData *data);
static gboolean contact_list_store_contact_active_cb (ShowActiveData *data);
static gboolean contact_list_store_get_group_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
FindGroup *fg);
static void contact_list_store_get_group (EmpathyContactListStore *store,
const gchar *name,
GtkTreeIter *iter_group_to_set,
GtkTreeIter *iter_separator_to_set,
gboolean *created,
gboolean is_fake_group);
static gint contact_list_store_state_sort_func (GtkTreeModel *model,
GtkTreeIter *iter_a,
GtkTreeIter *iter_b,
gpointer user_data);
static gint contact_list_store_name_sort_func (GtkTreeModel *model,
GtkTreeIter *iter_a,
GtkTreeIter *iter_b,
gpointer user_data);
static gboolean contact_list_store_find_contact_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
FindContact *fc);
static GList * contact_list_store_find_contact (EmpathyContactListStore *store,
EmpathyContact *contact);
static gboolean contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
EmpathyContactListStore *store);
enum {
PROP_0,
PROP_CONTACT_LIST,
PROP_SHOW_OFFLINE,
PROP_SHOW_AVATARS,
PROP_SHOW_PROTOCOLS,
PROP_SHOW_GROUPS,
PROP_IS_COMPACT,
PROP_SORT_CRITERIUM
};
G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
static void
contact_list_store_chat_state_changed_cb (TpChannel *self,
guint contact_handle,
guint state,
gpointer store)
{
EmpathyContactListStorePriv *priv = GET_PRIV (store);
GList *contacts, *l;
contacts = empathy_contact_list_get_members (priv->list);
for (l = contacts; l != NULL; l = l->next) {
EmpathyContact *contact = EMPATHY_CONTACT (l->data);
if (empathy_contact_get_handle (contact) == contact_handle) {
contact_list_store_contact_update (store, contact);
break;
}
}
g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
g_list_free (contacts);
}
static gboolean
contact_list_store_iface_setup (gpointer user_data)
{
EmpathyContactListStore *store = user_data;
EmpathyContactListStorePriv *priv = GET_PRIV (store);
GList *contacts, *l;
/* Signal connection. */
g_signal_connect (priv->list,
"member-renamed",
G_CALLBACK (contact_list_store_member_renamed_cb),
store);
g_signal_connect (priv->list,
"members-changed",
G_CALLBACK (contact_list_store_members_changed_cb),
store);
g_signal_connect (priv->list,
"favourites-changed",
G_CALLBACK (contact_list_store_favourites_changed_cb),
store);
g_signal_connect (priv->list,
"groups-changed",
G_CALLBACK (contact_list_store_groups_changed_cb),
store);
if (EMPATHY_IS_TP_CHAT (priv->list)) {
g_signal_connect (priv->list,
"chat-state-changed",
G_CALLBACK (contact_list_store_chat_state_changed_cb),
store);
}
/* Add contacts already created. */
contacts = empathy_contact_list_get_members (priv->list);
for (l = contacts; l; l = l->next) {
contact_list_store_members_changed_cb (priv->list, l->data,
NULL, 0, NULL,
TRUE,
store);
g_object_unref (l->data);
}
g_list_free (contacts);
priv->setup_idle_id = 0;
return FALSE;
}
static void
contact_list_store_set_contact_list (EmpathyContactListStore *store,
EmpathyContactList *list_iface)
{
EmpathyContactListStorePriv *priv = GET_PRIV (store);
priv->list = g_object_ref (list_iface);
/* Let a chance to have all properties set before populating */
priv->setup_idle_id = g_idle_add (contact_list_store_iface_setup, store);
}
static void
empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = contact_list_store_dispose;
object_class->get_property = contact_list_store_get_property;
object_class->set_property = contact_list_store_set_property;
g_object_class_install_property (object_class,
PROP_CONTACT_LIST,
g_param_spec_object ("contact-list",
"The contact list iface",
"The contact list iface",
EMPATHY_TYPE_CONTACT_LIST,
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_CONTACT_LIST_STORE_SORT,
EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
G_PARAM_READWRITE));
g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
}
static void
empathy_contact_list_store_init (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (store,
EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv);
store->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) contact_list_store_inibit_active_cb,
store);
priv->status_icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
contact_list_store_setup (store);
}
static void
contact_list_store_dispose (GObject *object)
{
EmpathyContactListStorePriv *priv = GET_PRIV (object);
GList *contacts, *l;
if (priv->dispose_has_run)
return;
priv->dispose_has_run = TRUE;
contacts = empathy_contact_list_get_members (priv->list);
for (l = contacts; l; l = l->next) {
g_signal_handlers_disconnect_by_func (l->data,
G_CALLBACK (contact_list_store_contact_updated_cb),
object);
g_object_unref (l->data);
}
g_list_free (contacts);
g_signal_handlers_disconnect_by_func (priv->list,
G_CALLBACK (contact_list_store_member_renamed_cb),
object);
g_signal_handlers_disconnect_by_func (priv->list,
G_CALLBACK (contact_list_store_members_changed_cb),
object);
g_signal_handlers_disconnect_by_func (priv->list,
G_CALLBACK (contact_list_store_favourites_changed_cb),
object);
g_signal_handlers_disconnect_by_func (priv->list,
G_CALLBACK (contact_list_store_groups_changed_cb),
object);
g_object_unref (priv->list);
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_contact_list_store_parent_class)->dispose (object);
}
static void
contact_list_store_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec)
{
EmpathyContactListStorePriv *priv;
priv = GET_PRIV (object);
switch (param_id) {
case PROP_CONTACT_LIST:
g_value_set_object (value, priv->list);
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
contact_list_store_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec)
{
switch (param_id) {
case PROP_CONTACT_LIST:
contact_list_store_set_contact_list (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_object (value));
break;
case PROP_SHOW_OFFLINE:
empathy_contact_list_store_set_show_offline (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_boolean (value));
break;
case PROP_SHOW_AVATARS:
empathy_contact_list_store_set_show_avatars (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_boolean (value));
break;
case PROP_SHOW_PROTOCOLS:
empathy_contact_list_store_set_show_protocols (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_boolean (value));
break;
case PROP_SHOW_GROUPS:
empathy_contact_list_store_set_show_groups (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_boolean (value));
break;
case PROP_IS_COMPACT:
empathy_contact_list_store_set_is_compact (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_boolean (value));
break;
case PROP_SORT_CRITERIUM:
empathy_contact_list_store_set_sort_criterium (EMPATHY_CONTACT_LIST_STORE (object),
g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break;
};
}
EmpathyContactListStore *
empathy_contact_list_store_new (EmpathyContactList *list_iface)
{
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
return g_object_new (EMPATHY_TYPE_CONTACT_LIST_STORE,
"contact-list", list_iface,
NULL);
}
EmpathyContactList *
empathy_contact_list_store_get_list_iface (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
priv = GET_PRIV (store);
return priv->list;
}
gboolean
empathy_contact_list_store_get_show_offline (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
priv = GET_PRIV (store);
return priv->show_offline;
}
void
empathy_contact_list_store_set_show_offline (EmpathyContactListStore *store,
gboolean show_offline)
{
EmpathyContactListStorePriv *priv;
GList *contacts, *l;
gboolean show_active;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
priv->show_offline = show_offline;
show_active = priv->show_active;
/* Disable temporarily. */
priv->show_active = FALSE;
contacts = empathy_contact_list_get_members (priv->list);
for (l = contacts; l; l = l->next) {
contact_list_store_contact_update (store, l->data);
g_object_unref (l->data);
}
g_list_free (contacts);
/* Restore to original setting. */
priv->show_active = show_active;
g_object_notify (G_OBJECT (store), "show-offline");
}
gboolean
empathy_contact_list_store_get_show_avatars (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
priv = GET_PRIV (store);
return priv->show_avatars;
}
void
empathy_contact_list_store_set_show_avatars (EmpathyContactListStore *store,
gboolean show_avatars)
{
EmpathyContactListStorePriv *priv;
GtkTreeModel *model;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
priv->show_avatars = show_avatars;
model = GTK_TREE_MODEL (store);
gtk_tree_model_foreach (model,
(GtkTreeModelForeachFunc)
contact_list_store_update_list_mode_foreach,
store);
g_object_notify (G_OBJECT (store), "show-avatars");
}
gboolean
empathy_contact_list_store_get_show_protocols (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
priv = GET_PRIV (store);
return priv->show_protocols;
}
void
empathy_contact_list_store_set_show_protocols (EmpathyContactListStore *store,
gboolean show_protocols)
{
EmpathyContactListStorePriv *priv;
GtkTreeModel *model;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
priv->show_protocols = show_protocols;
model = GTK_TREE_MODEL (store);
gtk_tree_model_foreach (model,
(GtkTreeModelForeachFunc)
contact_list_store_update_list_mode_foreach,
store);
g_object_notify (G_OBJECT (store), "show-protocols");
}
gboolean
empathy_contact_list_store_get_show_groups (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
priv = GET_PRIV (store);
return priv->show_groups;
}
void
empathy_contact_list_store_set_show_groups (EmpathyContactListStore *store,
gboolean show_groups)
{
EmpathyContactListStorePriv *priv;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
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, *l;
gtk_tree_store_clear (GTK_TREE_STORE (store));
contacts = empathy_contact_list_get_members (priv->list);
for (l = contacts; l; l = l->next) {
contact_list_store_members_changed_cb (priv->list,
l->data,
NULL, 0, NULL,
TRUE,
store);
g_object_unref (l->data);
}
g_list_free (contacts);
}
g_object_notify (G_OBJECT (store), "show-groups");
}
gboolean
empathy_contact_list_store_get_is_compact (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
priv = GET_PRIV (store);
return priv->is_compact;
}
void
empathy_contact_list_store_set_is_compact (EmpathyContactListStore *store,
gboolean is_compact)
{
EmpathyContactListStorePriv *priv;
GtkTreeModel *model;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
priv->is_compact = is_compact;
model = GTK_TREE_MODEL (store);
gtk_tree_model_foreach (model,
(GtkTreeModelForeachFunc)
contact_list_store_update_list_mode_foreach,
store);
g_object_notify (G_OBJECT (store), "is-compact");
}
EmpathyContactListStoreSort
empathy_contact_list_store_get_sort_criterium (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), 0);
priv = GET_PRIV (store);
return priv->sort_criterium;
}
void
empathy_contact_list_store_set_sort_criterium (EmpathyContactListStore *store,
EmpathyContactListStoreSort sort_criterium)
{
EmpathyContactListStorePriv *priv;
g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
priv = GET_PRIV (store);
priv->sort_criterium = sort_criterium;
switch (sort_criterium) {
case EMPATHY_CONTACT_LIST_STORE_SORT_STATE:
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
GTK_SORT_ASCENDING);
break;
case EMPATHY_CONTACT_LIST_STORE_SORT_NAME:
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
EMPATHY_CONTACT_LIST_STORE_COL_NAME,
GTK_SORT_ASCENDING);
break;
default:
g_assert_not_reached ();
}
g_object_notify (G_OBJECT (store), "sort-criterium");
}
gboolean
empathy_contact_list_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_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
-1);
return is_separator;
}
gchar *
empathy_contact_list_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_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
EMPATHY_CONTACT_LIST_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_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
EMPATHY_CONTACT_LIST_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 void
contact_list_store_setup (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *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 */
EMPATHY_TYPE_CONTACT, /* Contact 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_CONTACT_LIST_FLAGS, /* Flags */
G_TYPE_BOOLEAN, /* Is a fake group */
};
priv = GET_PRIV (store);
gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
types);
/* Set up sorting */
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
EMPATHY_CONTACT_LIST_STORE_COL_NAME,
contact_list_store_name_sort_func,
store, NULL);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
contact_list_store_state_sort_func,
store, NULL);
priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
}
static gboolean
contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
priv = GET_PRIV (store);
priv->show_active = TRUE;
priv->inhibit_active = 0;
return FALSE;
}
static void
contact_list_store_add_contact_and_connect (EmpathyContactListStore *store, EmpathyContact *contact)
{
g_signal_connect (contact, "notify::presence",
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
g_signal_connect (contact, "notify::presence-message",
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
g_signal_connect (contact, "notify::name",
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
g_signal_connect (contact, "notify::avatar",
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
g_signal_connect (contact, "notify::capabilities",
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
contact_list_store_add_contact (store, contact);
}
static void
contact_list_store_remove_contact_and_disconnect (EmpathyContactListStore *store, EmpathyContact *contact)
{
g_signal_handlers_disconnect_by_func (contact,
G_CALLBACK (contact_list_store_contact_updated_cb),
store);
contact_list_store_remove_contact (store, contact);
}
static void
contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
EmpathyContact *actor,
guint reason,
gchar *message,
gboolean is_member,
EmpathyContactListStore *store)
{
DEBUG ("Contact %s (%d) %s",
empathy_contact_get_id (contact),
empathy_contact_get_handle (contact),
is_member ? "added" : "removed");
if (is_member) {
contact_list_store_add_contact_and_connect (store, contact);
} else {
contact_list_store_remove_contact_and_disconnect (store, contact);
}
}
static void
contact_list_store_favourites_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
gboolean is_favourite,
EmpathyContactListStore *store)
{
DEBUG ("Contact %s (%d) is %s a favourite",
empathy_contact_get_id (contact),
empathy_contact_get_handle (contact),
is_favourite ? "now" : "no longer");
contact_list_store_remove_contact (store, contact);
contact_list_store_add_contact (store, contact);
}
static void
contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
EmpathyContact *old_contact,
EmpathyContact *new_contact,
guint reason,
gchar *message,
EmpathyContactListStore *store)
{
DEBUG ("Contact %s (%d) renamed to %s (%d)",
empathy_contact_get_id (old_contact),
empathy_contact_get_handle (old_contact),
empathy_contact_get_id (new_contact),
empathy_contact_get_handle (new_contact));
/* add the new contact */
contact_list_store_add_contact_and_connect (store, new_contact);
/* remove old contact */
contact_list_store_remove_contact_and_disconnect (store, old_contact);
}
static void
contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
EmpathyContact *contact,
gchar *group,
gboolean is_member,
EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
gboolean show_active;
priv = GET_PRIV (store);
DEBUG ("Updating groups for contact %s (%d)",
empathy_contact_get_id (contact),
empathy_contact_get_handle (contact));
/* 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;
contact_list_store_remove_contact (store, contact);
contact_list_store_add_contact (store, contact);
priv->show_active = show_active;
}
static void
add_contact_to_store (GtkTreeStore *store,
GtkTreeIter *iter,
GtkTreeIter *parent,
EmpathyContact *contact,
EmpathyContactListFlags flags)
{
gtk_tree_store_insert_with_values (store, iter, parent, 0,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_alias (contact),
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
empathy_contact_get_capabilities (contact) &
EMPATHY_CAPABILITIES_AUDIO,
EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
empathy_contact_get_capabilities (contact) &
EMPATHY_CAPABILITIES_VIDEO,
EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
-1);
}
static void
contact_list_store_add_contact (EmpathyContactListStore *store,
EmpathyContact *contact)
{
EmpathyContactListStorePriv *priv;
GtkTreeIter iter;
GList *groups = NULL, *l;
TpConnection *connection;
EmpathyContactListFlags flags = 0;
char *protocol_name;
priv = GET_PRIV (store);
if (EMP_STR_EMPTY (empathy_contact_get_alias (contact)) ||
(!priv->show_offline && !empathy_contact_is_online (contact))) {
return;
}
if (priv->show_groups) {
groups = empathy_contact_list_get_groups (priv->list, contact);
}
connection = empathy_contact_get_connection (contact);
if (EMPATHY_IS_CONTACT_MANAGER (priv->list)) {
flags = empathy_contact_manager_get_flags_for_connection (
EMPATHY_CONTACT_MANAGER (priv->list), connection);
}
tp_connection_parse_object_path (connection, &protocol_name, NULL);
if (!groups) {
GtkTreeIter iter_group, *parent;
parent = &iter_group;
if (!priv->show_groups) {
parent = NULL;
} else if (!tp_strdiff (protocol_name, "local-xmpp")) {
/* these are People Nearby */
contact_list_store_get_group (store,
EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY,
&iter_group, NULL, NULL, TRUE);
} else {
contact_list_store_get_group (store,
EMPATHY_CONTACT_LIST_STORE_UNGROUPED,
&iter_group, NULL, NULL, TRUE);
}
add_contact_to_store (GTK_TREE_STORE (store), &iter, parent,
contact, flags);
}
g_free (protocol_name);
/* Else add to each group. */
for (l = groups; l; l = l->next) {
GtkTreeIter iter_group;
contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL, FALSE);
add_contact_to_store (GTK_TREE_STORE (store), &iter, &iter_group, contact, flags);
g_free (l->data);
}
g_list_free (groups);
if (priv->show_groups &&
empathy_contact_list_is_favourite (priv->list, contact)) {
/* Add contact to the fake 'Favorites' group */
GtkTreeIter iter_group;
contact_list_store_get_group (store, EMPATHY_CONTACT_LIST_STORE_FAVORITE,
&iter_group, NULL, NULL, TRUE);
add_contact_to_store (GTK_TREE_STORE (store), &iter, &iter_group, contact, flags);
}
contact_list_store_contact_update (store, contact);
}
static void
contact_list_store_remove_contact (EmpathyContactListStore *store,
EmpathyContact *contact)
{
GtkTreeModel *model;
GList *iters, *l;
iters = contact_list_store_find_contact (store, contact);
if (!iters) {
return;
}
/* Clean up model */
model = GTK_TREE_MODEL (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) <= 2) {
gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
} else {
gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
}
}
g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
g_list_free (iters);
}
static void
contact_list_store_contact_update (EmpathyContactListStore *store,
EmpathyContact *contact)
{
EmpathyContactListStorePriv *priv;
ShowActiveData *data;
GtkTreeModel *model;
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_avatar;
GdkPixbuf *pixbuf_status;
priv = GET_PRIV (store);
model = GTK_TREE_MODEL (store);
iters = contact_list_store_find_contact (store, contact);
if (!iters) {
in_list = FALSE;
} else {
in_list = TRUE;
}
/* Get online state now. */
now_online = empathy_contact_is_online (contact);
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 ("Contact:'%s' in list:NO, should be:NO",
empathy_contact_get_alias (contact));
g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
g_list_free (iters);
return;
}
else if (in_list && !should_be_in_list) {
DEBUG ("Contact:'%s' in list:YES, should be:NO",
empathy_contact_get_alias (contact));
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)!");
contact_list_store_remove_contact (store, contact);
}
}
else if (!in_list && should_be_in_list) {
DEBUG ("Contact:'%s' in list:NO, should be:YES",
empathy_contact_get_alias (contact));
contact_list_store_add_contact (store, contact);
if (priv->show_active) {
do_set_active = TRUE;
DEBUG ("Set active (contact added)");
}
} else {
DEBUG ("Contact:'%s' in list:YES, should be:YES",
empathy_contact_get_alias (contact));
/* Get online state before. */
if (iters && g_list_length (iters) > 0) {
gtk_tree_model_get (model, iters->data,
EMPATHY_CONTACT_LIST_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 (contact 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 (contact updated)");
}
}
set_model = TRUE;
}
if (priv->show_avatars && !priv->is_compact) {
show_avatar = TRUE;
}
pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
for (l = iters; l && set_model; l = l->next) {
gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_alias (contact),
EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE,
empathy_contact_get_presence (contact),
EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
empathy_contact_get_presence_message (contact),
EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
empathy_contact_get_capabilities (contact) &
EMPATHY_CAPABILITIES_AUDIO,
EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
empathy_contact_get_capabilities (contact) &
EMPATHY_CAPABILITIES_VIDEO,
-1);
}
if (pixbuf_avatar) {
g_object_unref (pixbuf_avatar);
}
if (priv->show_active && do_set_active) {
contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
if (do_set_active) {
data = contact_list_store_contact_active_new (store, contact, do_remove);
g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
(GSourceFunc) contact_list_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);
}
static void
contact_list_store_contact_updated_cb (EmpathyContact *contact,
GParamSpec *param,
EmpathyContactListStore *store)
{
DEBUG ("Contact:'%s' updated, checking roster is in sync...",
empathy_contact_get_alias (contact));
contact_list_store_contact_update (store, contact);
}
static void
contact_list_store_contact_set_active (EmpathyContactListStore *store,
EmpathyContact *contact,
gboolean active,
gboolean set_changed)
{
GtkTreeModel *model;
GList *iters, *l;
model = GTK_TREE_MODEL (store);
iters = contact_list_store_find_contact (store, contact);
for (l = iters; l; l = l->next) {
GtkTreePath *path;
gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
EMPATHY_CONTACT_LIST_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 *
contact_list_store_contact_active_new (EmpathyContactListStore *store,
EmpathyContact *contact,
gboolean remove_)
{
ShowActiveData *data;
DEBUG ("Contact:'%s' now active, and %s be removed",
empathy_contact_get_alias (contact),
remove_ ? "WILL" : "WILL NOT");
data = g_slice_new0 (ShowActiveData);
data->store = g_object_ref (store);
data->contact = g_object_ref (contact);
data->remove = remove_;
return data;
}
static void
contact_list_store_contact_active_free (ShowActiveData *data)
{
g_object_unref (data->contact);
g_object_unref (data->store);
g_slice_free (ShowActiveData, data);
}
static gboolean
contact_list_store_contact_active_cb (ShowActiveData *data)
{
EmpathyContactListStorePriv *priv;
priv = GET_PRIV (data->store);
if (data->remove &&
!priv->show_offline &&
!empathy_contact_is_online (data->contact)) {
DEBUG ("Contact:'%s' active timeout, removing item",
empathy_contact_get_alias (data->contact));
contact_list_store_remove_contact (data->store, data->contact);
}
DEBUG ("Contact:'%s' no longer active",
empathy_contact_get_alias (data->contact));
contact_list_store_contact_set_active (data->store,
data->contact,
FALSE,
TRUE);
contact_list_store_contact_active_free (data);
return FALSE;
}
static gboolean
contact_list_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_CONTACT_LIST_STORE_COL_NAME, &str,
EMPATHY_CONTACT_LIST_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
contact_list_store_get_group (EmpathyContactListStore *store,
const gchar *name,
GtkTreeIter *iter_group_to_set,
GtkTreeIter *iter_separator_to_set,
gboolean *created,
gboolean is_fake_group)
{
GtkTreeModel *model;
GtkTreeIter iter_group;
GtkTreeIter iter_separator;
FindGroup fg;
memset (&fg, 0, sizeof (fg));
fg.name = name;
model = GTK_TREE_MODEL (store);
gtk_tree_model_foreach (model,
(GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
&fg);
if (!fg.found) {
if (created) {
*created = TRUE;
}
gtk_tree_store_insert_with_values (GTK_TREE_STORE (store), &iter_group, NULL, 0,
EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
EMPATHY_CONTACT_LIST_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 (store), &iter_separator, &iter_group, 0,
EMPATHY_CONTACT_LIST_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_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
-1);
if (is_separator && iter_separator_to_set) {
*iter_separator_to_set = iter_separator;
}
}
}
}
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,
EmpathyContact *contact_a,
EmpathyContact *contact_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_CONTACT_LIST_STORE_FAVORITE,
NULL
};
const char *bottom_groups[] = {
EMPATHY_CONTACT_LIST_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 (!contact_a && contact_b) {
return 1;
} else if (contact_a && !contact_b) {
return -1;
} else if (!contact_a && !contact_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
contact_list_store_contact_sort (EmpathyContact *contact_a,
EmpathyContact *contact_b)
{
TpAccount *account_a, *account_b;
gint ret_val;
g_return_val_if_fail (contact_a != NULL || contact_b != NULL, 0);
/* alias */
ret_val = g_utf8_collate (empathy_contact_get_alias (contact_a),
empathy_contact_get_alias (contact_b));
if (ret_val != 0)
goto out;
/* identifier */
ret_val = g_utf8_collate (empathy_contact_get_id (contact_a),
empathy_contact_get_id (contact_b));
if (ret_val != 0)
goto out;
account_a = empathy_contact_get_account (contact_a);
account_b = empathy_contact_get_account (contact_b);
/* protocol */
ret_val = strcmp (tp_account_get_protocol (account_a),
tp_account_get_protocol (account_b));
if (ret_val != 0)
goto out;
/* account ID */
ret_val = strcmp (tp_proxy_get_object_path (account_a),
tp_proxy_get_object_path (account_b));
out:
return ret_val;
}
static gint
contact_list_store_state_sort_func (GtkTreeModel *model,
GtkTreeIter *iter_a,
GtkTreeIter *iter_b,
gpointer user_data)
{
gint ret_val;
gchar *name_a, *name_b;
gboolean is_separator_a, is_separator_b;
EmpathyContact *contact_a, *contact_b;
gboolean fake_group_a, fake_group_b;
gtk_tree_model_get (model, iter_a,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
-1);
gtk_tree_model_get (model, iter_b,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
-1);
if (contact_a == NULL || contact_b == NULL) {
ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
name_a, name_b, contact_a, contact_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.
*/
ret_val = -tp_connection_presence_type_cmp_availability (
empathy_contact_get_presence (EMPATHY_CONTACT (contact_a)),
empathy_contact_get_presence (EMPATHY_CONTACT (contact_b)));
if (ret_val == 0) {
/* Fallback: compare by name et al. */
ret_val = contact_list_store_contact_sort (contact_a, contact_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_store_name_sort_func (GtkTreeModel *model,
GtkTreeIter *iter_a,
GtkTreeIter *iter_b,
gpointer user_data)
{
gchar *name_a, *name_b;
EmpathyContact *contact_a, *contact_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_CONTACT_LIST_STORE_COL_NAME, &name_a,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
-1);
gtk_tree_model_get (model, iter_b,
EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
-1);
if (contact_a == NULL || contact_b == NULL)
ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
name_a, name_b, contact_a, contact_b, fake_group_a, fake_group_b);
else
ret_val = contact_list_store_contact_sort (contact_a, contact_b);
if (contact_a) {
g_object_unref (contact_a);
}
if (contact_b) {
g_object_unref (contact_b);
}
return ret_val;
}
static gboolean
contact_list_store_find_contact_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
FindContact *fc)
{
EmpathyContact *contact;
gtk_tree_model_get (model, iter,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
-1);
if (contact == fc->contact) {
fc->found = TRUE;
fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
}
if (contact) {
g_object_unref (contact);
}
return FALSE;
}
static GList *
contact_list_store_find_contact (EmpathyContactListStore *store,
EmpathyContact *contact)
{
GtkTreeModel *model;
GList *l = NULL;
FindContact fc;
memset (&fc, 0, sizeof (fc));
fc.contact = contact;
model = GTK_TREE_MODEL (store);
gtk_tree_model_foreach (model,
(GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
&fc);
if (fc.found) {
l = fc.iters;
}
return l;
}
static gboolean
contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
EmpathyContactListStore *store)
{
EmpathyContactListStorePriv *priv;
gboolean show_avatar = FALSE;
EmpathyContact *contact;
GdkPixbuf *pixbuf_status;
priv = GET_PRIV (store);
if (priv->show_avatars && !priv->is_compact) {
show_avatar = TRUE;
}
gtk_tree_model_get (model, iter,
EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
-1);
if (contact == NULL){
return FALSE;
}
/* get icon from hash_table */
pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
gtk_tree_store_set (GTK_TREE_STORE (store), iter,
EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
-1);
return FALSE;
}
static GdkPixbuf *
contact_list_store_get_contact_status_icon_with_icon_name (
EmpathyContactListStore *store,
EmpathyContact *contact,
const gchar *status_icon_name)
{
GdkPixbuf *pixbuf_status = NULL;
EmpathyContactListStorePriv *priv;
const gchar *protocol_name = NULL;
gchar *icon_name = NULL;
priv = GET_PRIV (store);
if (priv->show_protocols) {
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);
}
pixbuf_status = g_hash_table_lookup (priv->status_icons, 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);
return pixbuf_status;
}
GdkPixbuf *
contact_list_store_get_contact_status_icon (EmpathyContactListStore *store,
EmpathyContact *contact)
{
GdkPixbuf *pixbuf_status = NULL;
EmpathyContactListStorePriv *priv;
const gchar *status_icon_name = NULL;
gboolean composing = FALSE;
priv = GET_PRIV (store);
if (EMPATHY_IS_TP_CHAT (priv->list)) {
if (empathy_tp_chat_get_chat_state (EMPATHY_TP_CHAT (priv->list),
contact) ==
TP_CHANNEL_CHAT_STATE_COMPOSING)
composing = TRUE;
}
if (composing) {
status_icon_name = EMPATHY_IMAGE_TYPING;
} else {
status_icon_name = empathy_icon_name_for_contact (contact);
}
if (status_icon_name == NULL)
return NULL;
pixbuf_status = contact_list_store_get_contact_status_icon_with_icon_name (
store,
contact,
status_icon_name);
return pixbuf_status;
}