1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
4 * Copyright (C) 2007-2008 Collabora Ltd.
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301 USA
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
31 #include <glib/gi18n-lib.h>
34 #include <telepathy-glib/util.h>
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy/empathy-tp-chat.h>
38 #include <libempathy/empathy-enum-types.h>
39 #include <libempathy/empathy-contact-manager.h>
41 #include "empathy-contact-list-store.h"
42 #include "empathy-ui-utils.h"
43 #include "empathy-gtk-enum-types.h"
44 #include "empathy-images.h"
46 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
47 #include <libempathy/empathy-debug.h>
49 /* Active users are those which have recently changed state
50 * (e.g. online, offline or from normal to a busy state).
53 /* Time in seconds user is shown as active */
54 #define ACTIVE_USER_SHOW_TIME 7
56 /* Time in seconds after connecting which we wait before active users are enabled */
57 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
59 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListStore)
61 EmpathyContactList *list;
62 gboolean show_offline;
63 gboolean show_avatars;
66 gboolean show_protocols;
68 EmpathyContactListStoreSort sort_criterium;
71 gboolean dispose_has_run;
72 GHashTable *status_icons;
73 } EmpathyContactListStorePriv;
82 EmpathyContact *contact;
88 EmpathyContactListStore *store;
89 EmpathyContact *contact;
93 static void contact_list_store_dispose (GObject *object);
94 static void contact_list_store_get_property (GObject *object,
98 static void contact_list_store_set_property (GObject *object,
102 static void contact_list_store_setup (EmpathyContactListStore *store);
103 static gboolean contact_list_store_inibit_active_cb (EmpathyContactListStore *store);
104 static void contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
105 EmpathyContact *contact,
106 EmpathyContact *actor,
110 EmpathyContactListStore *store);
111 static void contact_list_store_favourites_changed_cb (EmpathyContactList *list_iface,
112 EmpathyContact *contact,
113 gboolean is_favourite,
114 EmpathyContactListStore *store);
115 static void contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
116 EmpathyContact *old_contact,
117 EmpathyContact *new_contact,
120 EmpathyContactListStore *store);
121 static void contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
122 EmpathyContact *contact,
125 EmpathyContactListStore *store);
126 static void contact_list_store_add_contact (EmpathyContactListStore *store,
127 EmpathyContact *contact);
128 static void contact_list_store_remove_contact (EmpathyContactListStore *store,
129 EmpathyContact *contact);
130 static void contact_list_store_contact_update (EmpathyContactListStore *store,
131 EmpathyContact *contact);
132 static void contact_list_store_contact_updated_cb (EmpathyContact *contact,
134 EmpathyContactListStore *store);
135 static void contact_list_store_contact_set_active (EmpathyContactListStore *store,
136 EmpathyContact *contact,
138 gboolean set_changed);
139 static ShowActiveData * contact_list_store_contact_active_new (EmpathyContactListStore *store,
140 EmpathyContact *contact,
142 static void contact_list_store_contact_active_free (ShowActiveData *data);
143 static gboolean contact_list_store_contact_active_cb (ShowActiveData *data);
144 static gboolean contact_list_store_get_group_foreach (GtkTreeModel *model,
148 static void contact_list_store_get_group (EmpathyContactListStore *store,
150 GtkTreeIter *iter_group_to_set,
151 GtkTreeIter *iter_separator_to_set,
153 gboolean is_fake_group);
154 static gint contact_list_store_state_sort_func (GtkTreeModel *model,
158 static gint contact_list_store_name_sort_func (GtkTreeModel *model,
162 static gboolean contact_list_store_find_contact_foreach (GtkTreeModel *model,
166 static GList * contact_list_store_find_contact (EmpathyContactListStore *store,
167 EmpathyContact *contact);
168 static gboolean contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
171 EmpathyContactListStore *store);
184 G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
187 contact_list_store_chat_state_changed_cb (TpChannel *self,
188 guint contact_handle,
192 EmpathyContactListStorePriv *priv = GET_PRIV (store);
195 contacts = empathy_contact_list_get_members (priv->list);
197 for (l = contacts; l != NULL; l = l->next) {
198 EmpathyContact *contact = EMPATHY_CONTACT (l->data);
200 if (empathy_contact_get_handle (contact) == contact_handle) {
201 contact_list_store_contact_update (store, contact);
206 g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
207 g_list_free (contacts);
211 contact_list_store_iface_setup (gpointer user_data)
213 EmpathyContactListStore *store = user_data;
214 EmpathyContactListStorePriv *priv = GET_PRIV (store);
217 /* Signal connection. */
218 g_signal_connect (priv->list,
220 G_CALLBACK (contact_list_store_member_renamed_cb),
222 g_signal_connect (priv->list,
224 G_CALLBACK (contact_list_store_members_changed_cb),
226 g_signal_connect (priv->list,
227 "favourites-changed",
228 G_CALLBACK (contact_list_store_favourites_changed_cb),
230 g_signal_connect (priv->list,
232 G_CALLBACK (contact_list_store_groups_changed_cb),
235 if (EMPATHY_IS_TP_CHAT (priv->list)) {
238 channel = empathy_tp_chat_get_channel (EMPATHY_TP_CHAT (priv->list));
239 if (!tp_proxy_is_prepared (channel, TP_CHANNEL_FEATURE_CHAT_STATES)) {
240 DEBUG ("Chat state feature not prepared");
242 g_signal_connect (channel,
243 "chat-state-changed",
244 G_CALLBACK (contact_list_store_chat_state_changed_cb),
249 /* Add contacts already created. */
250 contacts = empathy_contact_list_get_members (priv->list);
251 for (l = contacts; l; l = l->next) {
252 contact_list_store_members_changed_cb (priv->list, l->data,
257 g_object_unref (l->data);
259 g_list_free (contacts);
261 priv->setup_idle_id = 0;
267 contact_list_store_set_contact_list (EmpathyContactListStore *store,
268 EmpathyContactList *list_iface)
270 EmpathyContactListStorePriv *priv = GET_PRIV (store);
272 priv->list = g_object_ref (list_iface);
274 /* Let a chance to have all properties set before populating */
275 priv->setup_idle_id = g_idle_add (contact_list_store_iface_setup, store);
279 empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
281 GObjectClass *object_class = G_OBJECT_CLASS (klass);
283 object_class->dispose = contact_list_store_dispose;
284 object_class->get_property = contact_list_store_get_property;
285 object_class->set_property = contact_list_store_set_property;
287 g_object_class_install_property (object_class,
289 g_param_spec_object ("contact-list",
290 "The contact list iface",
291 "The contact list iface",
292 EMPATHY_TYPE_CONTACT_LIST,
293 G_PARAM_CONSTRUCT_ONLY |
295 g_object_class_install_property (object_class,
297 g_param_spec_boolean ("show-offline",
299 "Whether contact list should display "
303 g_object_class_install_property (object_class,
305 g_param_spec_boolean ("show-avatars",
307 "Whether contact list should display "
308 "avatars for contacts",
311 g_object_class_install_property (object_class,
313 g_param_spec_boolean ("show-protocols",
315 "Whether contact list should display "
316 "protocols for contacts",
319 g_object_class_install_property (object_class,
321 g_param_spec_boolean ("show-groups",
323 "Whether contact list should display "
327 g_object_class_install_property (object_class,
329 g_param_spec_boolean ("is-compact",
331 "Whether the contact list is in compact mode or not",
335 g_object_class_install_property (object_class,
337 g_param_spec_enum ("sort-criterium",
339 "The sort criterium to use for sorting the contact list",
340 EMPATHY_TYPE_CONTACT_LIST_STORE_SORT,
341 EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
344 g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
348 empathy_contact_list_store_init (EmpathyContactListStore *store)
350 EmpathyContactListStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (store,
351 EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv);
354 priv->show_avatars = TRUE;
355 priv->show_groups = TRUE;
356 priv->show_protocols = FALSE;
357 priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
358 (GSourceFunc) contact_list_store_inibit_active_cb,
360 priv->status_icons = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
361 contact_list_store_setup (store);
365 contact_list_store_dispose (GObject *object)
367 EmpathyContactListStorePriv *priv = GET_PRIV (object);
370 if (priv->dispose_has_run)
372 priv->dispose_has_run = TRUE;
374 contacts = empathy_contact_list_get_members (priv->list);
375 for (l = contacts; l; l = l->next) {
376 g_signal_handlers_disconnect_by_func (l->data,
377 G_CALLBACK (contact_list_store_contact_updated_cb),
380 g_object_unref (l->data);
382 g_list_free (contacts);
384 g_signal_handlers_disconnect_by_func (priv->list,
385 G_CALLBACK (contact_list_store_member_renamed_cb),
387 g_signal_handlers_disconnect_by_func (priv->list,
388 G_CALLBACK (contact_list_store_members_changed_cb),
390 g_signal_handlers_disconnect_by_func (priv->list,
391 G_CALLBACK (contact_list_store_favourites_changed_cb),
393 g_signal_handlers_disconnect_by_func (priv->list,
394 G_CALLBACK (contact_list_store_groups_changed_cb),
396 g_object_unref (priv->list);
398 if (priv->inhibit_active) {
399 g_source_remove (priv->inhibit_active);
402 if (priv->setup_idle_id != 0) {
403 g_source_remove (priv->setup_idle_id);
406 g_hash_table_destroy (priv->status_icons);
407 G_OBJECT_CLASS (empathy_contact_list_store_parent_class)->dispose (object);
411 contact_list_store_get_property (GObject *object,
416 EmpathyContactListStorePriv *priv;
418 priv = GET_PRIV (object);
421 case PROP_CONTACT_LIST:
422 g_value_set_object (value, priv->list);
424 case PROP_SHOW_OFFLINE:
425 g_value_set_boolean (value, priv->show_offline);
427 case PROP_SHOW_AVATARS:
428 g_value_set_boolean (value, priv->show_avatars);
430 case PROP_SHOW_PROTOCOLS:
431 g_value_set_boolean (value, priv->show_protocols);
433 case PROP_SHOW_GROUPS:
434 g_value_set_boolean (value, priv->show_groups);
436 case PROP_IS_COMPACT:
437 g_value_set_boolean (value, priv->is_compact);
439 case PROP_SORT_CRITERIUM:
440 g_value_set_enum (value, priv->sort_criterium);
443 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
449 contact_list_store_set_property (GObject *object,
455 case PROP_CONTACT_LIST:
456 contact_list_store_set_contact_list (EMPATHY_CONTACT_LIST_STORE (object),
457 g_value_get_object (value));
459 case PROP_SHOW_OFFLINE:
460 empathy_contact_list_store_set_show_offline (EMPATHY_CONTACT_LIST_STORE (object),
461 g_value_get_boolean (value));
463 case PROP_SHOW_AVATARS:
464 empathy_contact_list_store_set_show_avatars (EMPATHY_CONTACT_LIST_STORE (object),
465 g_value_get_boolean (value));
467 case PROP_SHOW_PROTOCOLS:
468 empathy_contact_list_store_set_show_protocols (EMPATHY_CONTACT_LIST_STORE (object),
469 g_value_get_boolean (value));
471 case PROP_SHOW_GROUPS:
472 empathy_contact_list_store_set_show_groups (EMPATHY_CONTACT_LIST_STORE (object),
473 g_value_get_boolean (value));
475 case PROP_IS_COMPACT:
476 empathy_contact_list_store_set_is_compact (EMPATHY_CONTACT_LIST_STORE (object),
477 g_value_get_boolean (value));
479 case PROP_SORT_CRITERIUM:
480 empathy_contact_list_store_set_sort_criterium (EMPATHY_CONTACT_LIST_STORE (object),
481 g_value_get_enum (value));
484 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
489 EmpathyContactListStore *
490 empathy_contact_list_store_new (EmpathyContactList *list_iface)
492 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
494 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_STORE,
495 "contact-list", list_iface,
500 empathy_contact_list_store_get_list_iface (EmpathyContactListStore *store)
502 EmpathyContactListStorePriv *priv;
504 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
506 priv = GET_PRIV (store);
512 empathy_contact_list_store_get_show_offline (EmpathyContactListStore *store)
514 EmpathyContactListStorePriv *priv;
516 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
518 priv = GET_PRIV (store);
520 return priv->show_offline;
524 empathy_contact_list_store_set_show_offline (EmpathyContactListStore *store,
525 gboolean show_offline)
527 EmpathyContactListStorePriv *priv;
529 gboolean show_active;
531 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
533 priv = GET_PRIV (store);
535 priv->show_offline = show_offline;
536 show_active = priv->show_active;
538 /* Disable temporarily. */
539 priv->show_active = FALSE;
541 contacts = empathy_contact_list_get_members (priv->list);
542 for (l = contacts; l; l = l->next) {
543 contact_list_store_contact_update (store, l->data);
545 g_object_unref (l->data);
547 g_list_free (contacts);
549 /* Restore to original setting. */
550 priv->show_active = show_active;
552 g_object_notify (G_OBJECT (store), "show-offline");
556 empathy_contact_list_store_get_show_avatars (EmpathyContactListStore *store)
558 EmpathyContactListStorePriv *priv;
560 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
562 priv = GET_PRIV (store);
564 return priv->show_avatars;
568 empathy_contact_list_store_set_show_avatars (EmpathyContactListStore *store,
569 gboolean show_avatars)
571 EmpathyContactListStorePriv *priv;
574 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
576 priv = GET_PRIV (store);
578 priv->show_avatars = show_avatars;
580 model = GTK_TREE_MODEL (store);
582 gtk_tree_model_foreach (model,
583 (GtkTreeModelForeachFunc)
584 contact_list_store_update_list_mode_foreach,
587 g_object_notify (G_OBJECT (store), "show-avatars");
592 empathy_contact_list_store_get_show_protocols (EmpathyContactListStore *store)
594 EmpathyContactListStorePriv *priv;
596 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
598 priv = GET_PRIV (store);
600 return priv->show_protocols;
604 empathy_contact_list_store_set_show_protocols (EmpathyContactListStore *store,
605 gboolean show_protocols)
607 EmpathyContactListStorePriv *priv;
610 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
612 priv = GET_PRIV (store);
614 priv->show_protocols = show_protocols;
616 model = GTK_TREE_MODEL (store);
618 gtk_tree_model_foreach (model,
619 (GtkTreeModelForeachFunc)
620 contact_list_store_update_list_mode_foreach,
623 g_object_notify (G_OBJECT (store), "show-protocols");
627 empathy_contact_list_store_get_show_groups (EmpathyContactListStore *store)
629 EmpathyContactListStorePriv *priv;
631 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
633 priv = GET_PRIV (store);
635 return priv->show_groups;
639 empathy_contact_list_store_set_show_groups (EmpathyContactListStore *store,
640 gboolean show_groups)
642 EmpathyContactListStorePriv *priv;
644 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
646 priv = GET_PRIV (store);
648 if (priv->show_groups == show_groups) {
652 priv->show_groups = show_groups;
654 if (priv->setup_idle_id == 0) {
655 /* Remove all contacts and add them back, not optimized but
656 * that's the easy way :)
658 * This is only done if there's not a pending setup idle
659 * callback, otherwise it will race and the contacts will get
663 gtk_tree_store_clear (GTK_TREE_STORE (store));
664 contacts = empathy_contact_list_get_members (priv->list);
665 for (l = contacts; l; l = l->next) {
666 contact_list_store_members_changed_cb (priv->list,
672 g_object_unref (l->data);
674 g_list_free (contacts);
677 g_object_notify (G_OBJECT (store), "show-groups");
681 empathy_contact_list_store_get_is_compact (EmpathyContactListStore *store)
683 EmpathyContactListStorePriv *priv;
685 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
687 priv = GET_PRIV (store);
689 return priv->is_compact;
693 empathy_contact_list_store_set_is_compact (EmpathyContactListStore *store,
696 EmpathyContactListStorePriv *priv;
699 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
701 priv = GET_PRIV (store);
703 priv->is_compact = is_compact;
705 model = GTK_TREE_MODEL (store);
707 gtk_tree_model_foreach (model,
708 (GtkTreeModelForeachFunc)
709 contact_list_store_update_list_mode_foreach,
712 g_object_notify (G_OBJECT (store), "is-compact");
715 EmpathyContactListStoreSort
716 empathy_contact_list_store_get_sort_criterium (EmpathyContactListStore *store)
718 EmpathyContactListStorePriv *priv;
720 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), 0);
722 priv = GET_PRIV (store);
724 return priv->sort_criterium;
728 empathy_contact_list_store_set_sort_criterium (EmpathyContactListStore *store,
729 EmpathyContactListStoreSort sort_criterium)
731 EmpathyContactListStorePriv *priv;
733 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
735 priv = GET_PRIV (store);
737 priv->sort_criterium = sort_criterium;
739 switch (sort_criterium) {
740 case EMPATHY_CONTACT_LIST_STORE_SORT_STATE:
741 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
742 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
746 case EMPATHY_CONTACT_LIST_STORE_SORT_NAME:
747 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
748 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
753 g_assert_not_reached ();
756 g_object_notify (G_OBJECT (store), "sort-criterium");
760 empathy_contact_list_store_row_separator_func (GtkTreeModel *model,
764 gboolean is_separator = FALSE;
766 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
768 gtk_tree_model_get (model, iter,
769 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
776 empathy_contact_list_store_get_parent_group (GtkTreeModel *model,
778 gboolean *path_is_group,
779 gboolean *is_fake_group)
781 GtkTreeIter parent_iter, iter;
786 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
789 *path_is_group = FALSE;
792 if (!gtk_tree_model_get_iter (model, &iter, path)) {
796 gtk_tree_model_get (model, &iter,
797 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
798 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
805 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
811 gtk_tree_model_get (model, &iter,
812 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
813 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
814 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
823 *path_is_group = TRUE;
826 if (is_fake_group != NULL)
827 *is_fake_group = fake;
833 contact_list_store_setup (EmpathyContactListStore *store)
835 EmpathyContactListStorePriv *priv;
837 GDK_TYPE_PIXBUF, /* Status pixbuf */
838 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
839 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
840 G_TYPE_STRING, /* Name */
841 G_TYPE_UINT, /* Presence type */
842 G_TYPE_STRING, /* Status string */
843 G_TYPE_BOOLEAN, /* Compact view */
844 EMPATHY_TYPE_CONTACT, /* Contact type */
845 G_TYPE_BOOLEAN, /* Is group */
846 G_TYPE_BOOLEAN, /* Is active */
847 G_TYPE_BOOLEAN, /* Is online */
848 G_TYPE_BOOLEAN, /* Is separator */
849 G_TYPE_BOOLEAN, /* Can make audio calls */
850 G_TYPE_BOOLEAN, /* Can make video calls */
851 EMPATHY_TYPE_CONTACT_LIST_FLAGS, /* Flags */
852 G_TYPE_BOOLEAN, /* Is a fake group */
855 priv = GET_PRIV (store);
857 gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
858 EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
862 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
863 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
864 contact_list_store_name_sort_func,
866 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
867 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
868 contact_list_store_state_sort_func,
871 priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
872 empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
876 contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
878 EmpathyContactListStorePriv *priv;
880 priv = GET_PRIV (store);
882 priv->show_active = TRUE;
883 priv->inhibit_active = 0;
889 contact_list_store_add_contact_and_connect (EmpathyContactListStore *store, EmpathyContact *contact)
891 g_signal_connect (contact, "notify::presence",
892 G_CALLBACK (contact_list_store_contact_updated_cb),
894 g_signal_connect (contact, "notify::presence-message",
895 G_CALLBACK (contact_list_store_contact_updated_cb),
897 g_signal_connect (contact, "notify::name",
898 G_CALLBACK (contact_list_store_contact_updated_cb),
900 g_signal_connect (contact, "notify::avatar",
901 G_CALLBACK (contact_list_store_contact_updated_cb),
903 g_signal_connect (contact, "notify::capabilities",
904 G_CALLBACK (contact_list_store_contact_updated_cb),
907 contact_list_store_add_contact (store, contact);
911 contact_list_store_remove_contact_and_disconnect (EmpathyContactListStore *store, EmpathyContact *contact)
913 g_signal_handlers_disconnect_by_func (contact,
914 G_CALLBACK (contact_list_store_contact_updated_cb),
917 contact_list_store_remove_contact (store, contact);
921 contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
922 EmpathyContact *contact,
923 EmpathyContact *actor,
927 EmpathyContactListStore *store)
929 DEBUG ("Contact %s (%d) %s",
930 empathy_contact_get_id (contact),
931 empathy_contact_get_handle (contact),
932 is_member ? "added" : "removed");
935 contact_list_store_add_contact_and_connect (store, contact);
937 contact_list_store_remove_contact_and_disconnect (store, contact);
942 contact_list_store_favourites_changed_cb (EmpathyContactList *list_iface,
943 EmpathyContact *contact,
944 gboolean is_favourite,
945 EmpathyContactListStore *store)
947 DEBUG ("Contact %s (%d) is %s a favourite",
948 empathy_contact_get_id (contact),
949 empathy_contact_get_handle (contact),
950 is_favourite ? "now" : "no longer");
952 contact_list_store_remove_contact (store, contact);
953 contact_list_store_add_contact (store, contact);
957 contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
958 EmpathyContact *old_contact,
959 EmpathyContact *new_contact,
962 EmpathyContactListStore *store)
964 DEBUG ("Contact %s (%d) renamed to %s (%d)",
965 empathy_contact_get_id (old_contact),
966 empathy_contact_get_handle (old_contact),
967 empathy_contact_get_id (new_contact),
968 empathy_contact_get_handle (new_contact));
970 /* add the new contact */
971 contact_list_store_add_contact_and_connect (store, new_contact);
973 /* remove old contact */
974 contact_list_store_remove_contact_and_disconnect (store, old_contact);
978 contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
979 EmpathyContact *contact,
982 EmpathyContactListStore *store)
984 EmpathyContactListStorePriv *priv;
985 gboolean show_active;
987 priv = GET_PRIV (store);
989 DEBUG ("Updating groups for contact %s (%d)",
990 empathy_contact_get_id (contact),
991 empathy_contact_get_handle (contact));
993 /* We do this to make sure the groups are correct, if not, we
994 * would have to check the groups already set up for each
995 * contact and then see what has been updated.
997 show_active = priv->show_active;
998 priv->show_active = FALSE;
999 contact_list_store_remove_contact (store, contact);
1000 contact_list_store_add_contact (store, contact);
1001 priv->show_active = show_active;
1005 add_contact_to_store (GtkTreeStore *store,
1007 GtkTreeIter *parent,
1008 EmpathyContact *contact,
1009 EmpathyContactListFlags flags)
1011 gtk_tree_store_insert_with_values (store, iter, parent, 0,
1012 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_alias (contact),
1013 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
1014 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1015 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1016 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1017 empathy_contact_get_capabilities (contact) &
1018 EMPATHY_CAPABILITIES_AUDIO,
1019 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1020 empathy_contact_get_capabilities (contact) &
1021 EMPATHY_CAPABILITIES_VIDEO,
1022 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
1027 contact_list_store_add_contact (EmpathyContactListStore *store,
1028 EmpathyContact *contact)
1030 EmpathyContactListStorePriv *priv;
1032 GList *groups = NULL, *l;
1033 TpConnection *connection;
1034 EmpathyContactListFlags flags = 0;
1035 char *protocol_name;
1037 priv = GET_PRIV (store);
1039 if (EMP_STR_EMPTY (empathy_contact_get_alias (contact)) ||
1040 (!priv->show_offline && !empathy_contact_is_online (contact))) {
1044 if (priv->show_groups) {
1045 groups = empathy_contact_list_get_groups (priv->list, contact);
1048 connection = empathy_contact_get_connection (contact);
1049 if (EMPATHY_IS_CONTACT_MANAGER (priv->list)) {
1050 flags = empathy_contact_manager_get_flags_for_connection (
1051 EMPATHY_CONTACT_MANAGER (priv->list), connection);
1054 tp_connection_parse_object_path (connection, &protocol_name, NULL);
1057 GtkTreeIter iter_group, *parent;
1059 parent = &iter_group;
1061 if (!priv->show_groups) {
1063 } else if (!tp_strdiff (protocol_name, "local-xmpp")) {
1064 /* these are People Nearby */
1065 contact_list_store_get_group (store,
1066 EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY,
1067 &iter_group, NULL, NULL, TRUE);
1069 contact_list_store_get_group (store,
1070 EMPATHY_CONTACT_LIST_STORE_UNGROUPED,
1071 &iter_group, NULL, NULL, TRUE);
1074 add_contact_to_store (GTK_TREE_STORE (store), &iter, parent,
1078 g_free (protocol_name);
1080 /* Else add to each group. */
1081 for (l = groups; l; l = l->next) {
1082 GtkTreeIter iter_group;
1084 contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL, FALSE);
1086 add_contact_to_store (GTK_TREE_STORE (store), &iter, &iter_group, contact, flags);
1089 g_list_free (groups);
1091 if (priv->show_groups &&
1092 empathy_contact_list_is_favourite (priv->list, contact)) {
1093 /* Add contact to the fake 'Favorites' group */
1094 GtkTreeIter iter_group;
1096 contact_list_store_get_group (store, EMPATHY_CONTACT_LIST_STORE_FAVORITE,
1097 &iter_group, NULL, NULL, TRUE);
1099 add_contact_to_store (GTK_TREE_STORE (store), &iter, &iter_group, contact, flags);
1102 contact_list_store_contact_update (store, contact);
1106 contact_list_store_remove_contact (EmpathyContactListStore *store,
1107 EmpathyContact *contact)
1109 GtkTreeModel *model;
1112 iters = contact_list_store_find_contact (store, contact);
1117 /* Clean up model */
1118 model = GTK_TREE_MODEL (store);
1120 for (l = iters; l; l = l->next) {
1123 /* NOTE: it is only <= 2 here because we have
1124 * separators after the group name, otherwise it
1127 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1128 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1129 gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
1131 gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
1135 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1136 g_list_free (iters);
1140 contact_list_store_contact_update (EmpathyContactListStore *store,
1141 EmpathyContact *contact)
1143 EmpathyContactListStorePriv *priv;
1144 ShowActiveData *data;
1145 GtkTreeModel *model;
1148 gboolean should_be_in_list;
1149 gboolean was_online = TRUE;
1150 gboolean now_online = FALSE;
1151 gboolean set_model = FALSE;
1152 gboolean do_remove = FALSE;
1153 gboolean do_set_active = FALSE;
1154 gboolean do_set_refresh = FALSE;
1155 gboolean show_avatar = FALSE;
1156 GdkPixbuf *pixbuf_avatar;
1157 GdkPixbuf *pixbuf_status;
1159 priv = GET_PRIV (store);
1161 model = GTK_TREE_MODEL (store);
1163 iters = contact_list_store_find_contact (store, contact);
1170 /* Get online state now. */
1171 now_online = empathy_contact_is_online (contact);
1173 if (priv->show_offline || now_online) {
1174 should_be_in_list = TRUE;
1176 should_be_in_list = FALSE;
1179 if (!in_list && !should_be_in_list) {
1180 /* Nothing to do. */
1181 DEBUG ("Contact:'%s' in list:NO, should be:NO",
1182 empathy_contact_get_alias (contact));
1184 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1185 g_list_free (iters);
1188 else if (in_list && !should_be_in_list) {
1189 DEBUG ("Contact:'%s' in list:YES, should be:NO",
1190 empathy_contact_get_alias (contact));
1192 if (priv->show_active) {
1194 do_set_active = TRUE;
1195 do_set_refresh = TRUE;
1198 DEBUG ("Remove item (after timeout)");
1200 DEBUG ("Remove item (now)!");
1201 contact_list_store_remove_contact (store, contact);
1204 else if (!in_list && should_be_in_list) {
1205 DEBUG ("Contact:'%s' in list:NO, should be:YES",
1206 empathy_contact_get_alias (contact));
1208 contact_list_store_add_contact (store, contact);
1210 if (priv->show_active) {
1211 do_set_active = TRUE;
1213 DEBUG ("Set active (contact added)");
1216 DEBUG ("Contact:'%s' in list:YES, should be:YES",
1217 empathy_contact_get_alias (contact));
1219 /* Get online state before. */
1220 if (iters && g_list_length (iters) > 0) {
1221 gtk_tree_model_get (model, iters->data,
1222 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, &was_online,
1226 /* Is this really an update or an online/offline. */
1227 if (priv->show_active) {
1228 if (was_online != now_online) {
1229 do_set_active = TRUE;
1230 do_set_refresh = TRUE;
1232 DEBUG ("Set active (contact updated %s)",
1233 was_online ? "online -> offline" :
1234 "offline -> online");
1236 /* Was TRUE for presence updates. */
1237 /* do_set_active = FALSE; */
1238 do_set_refresh = TRUE;
1240 DEBUG ("Set active (contact updated)");
1247 if (priv->show_avatars && !priv->is_compact) {
1250 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1251 pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
1252 for (l = iters; l && set_model; l = l->next) {
1253 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1254 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
1255 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
1256 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1257 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_alias (contact),
1258 EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE,
1259 empathy_contact_get_presence (contact),
1260 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
1261 empathy_contact_get_presence_message (contact),
1262 EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
1263 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1264 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
1265 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1266 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1267 empathy_contact_get_capabilities (contact) &
1268 EMPATHY_CAPABILITIES_AUDIO,
1269 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1270 empathy_contact_get_capabilities (contact) &
1271 EMPATHY_CAPABILITIES_VIDEO,
1275 if (pixbuf_avatar) {
1276 g_object_unref (pixbuf_avatar);
1279 if (priv->show_active && do_set_active) {
1280 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1282 if (do_set_active) {
1283 data = contact_list_store_contact_active_new (store, contact, do_remove);
1284 g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
1285 (GSourceFunc) contact_list_store_contact_active_cb,
1290 /* FIXME: when someone goes online then offline quickly, the
1291 * first timeout sets the user to be inactive and the second
1292 * timeout removes the user from the contact list, really we
1293 * should remove the first timeout.
1295 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1296 g_list_free (iters);
1300 contact_list_store_contact_updated_cb (EmpathyContact *contact,
1302 EmpathyContactListStore *store)
1304 DEBUG ("Contact:'%s' updated, checking roster is in sync...",
1305 empathy_contact_get_alias (contact));
1307 contact_list_store_contact_update (store, contact);
1311 contact_list_store_contact_set_active (EmpathyContactListStore *store,
1312 EmpathyContact *contact,
1314 gboolean set_changed)
1316 GtkTreeModel *model;
1319 model = GTK_TREE_MODEL (store);
1321 iters = contact_list_store_find_contact (store, contact);
1322 for (l = iters; l; l = l->next) {
1325 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1326 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, active,
1329 DEBUG ("Set item %s", active ? "active" : "inactive");
1332 path = gtk_tree_model_get_path (model, l->data);
1333 gtk_tree_model_row_changed (model, path, l->data);
1334 gtk_tree_path_free (path);
1338 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1339 g_list_free (iters);
1343 static ShowActiveData *
1344 contact_list_store_contact_active_new (EmpathyContactListStore *store,
1345 EmpathyContact *contact,
1348 ShowActiveData *data;
1350 DEBUG ("Contact:'%s' now active, and %s be removed",
1351 empathy_contact_get_alias (contact),
1352 remove_ ? "WILL" : "WILL NOT");
1354 data = g_slice_new0 (ShowActiveData);
1356 data->store = g_object_ref (store);
1357 data->contact = g_object_ref (contact);
1358 data->remove = remove_;
1364 contact_list_store_contact_active_free (ShowActiveData *data)
1366 g_object_unref (data->contact);
1367 g_object_unref (data->store);
1369 g_slice_free (ShowActiveData, data);
1373 contact_list_store_contact_active_cb (ShowActiveData *data)
1375 EmpathyContactListStorePriv *priv;
1377 priv = GET_PRIV (data->store);
1380 !priv->show_offline &&
1381 !empathy_contact_is_online (data->contact)) {
1382 DEBUG ("Contact:'%s' active timeout, removing item",
1383 empathy_contact_get_alias (data->contact));
1384 contact_list_store_remove_contact (data->store, data->contact);
1387 DEBUG ("Contact:'%s' no longer active",
1388 empathy_contact_get_alias (data->contact));
1390 contact_list_store_contact_set_active (data->store,
1395 contact_list_store_contact_active_free (data);
1401 contact_list_store_get_group_foreach (GtkTreeModel *model,
1409 /* Groups are only at the top level. */
1410 if (gtk_tree_path_get_depth (path) != 1) {
1414 gtk_tree_model_get (model, iter,
1415 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &str,
1416 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1419 if (is_group && !tp_strdiff (str, fg->name)) {
1430 contact_list_store_get_group (EmpathyContactListStore *store,
1432 GtkTreeIter *iter_group_to_set,
1433 GtkTreeIter *iter_separator_to_set,
1435 gboolean is_fake_group)
1437 GtkTreeModel *model;
1438 GtkTreeIter iter_group;
1439 GtkTreeIter iter_separator;
1442 memset (&fg, 0, sizeof (fg));
1446 model = GTK_TREE_MODEL (store);
1447 gtk_tree_model_foreach (model,
1448 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1456 gtk_tree_store_insert_with_values (GTK_TREE_STORE (store), &iter_group, NULL, 0,
1457 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
1458 EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
1459 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
1460 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
1461 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1462 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, is_fake_group,
1465 if (iter_group_to_set) {
1466 *iter_group_to_set = iter_group;
1469 gtk_tree_store_insert_with_values (GTK_TREE_STORE (store), &iter_separator, &iter_group, 0,
1470 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, TRUE,
1473 if (iter_separator_to_set) {
1474 *iter_separator_to_set = iter_separator;
1481 if (iter_group_to_set) {
1482 *iter_group_to_set = fg.iter;
1485 iter_separator = fg.iter;
1487 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1488 gboolean is_separator;
1490 gtk_tree_model_get (model, &iter_separator,
1491 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1494 if (is_separator && iter_separator_to_set) {
1495 *iter_separator_to_set = iter_separator;
1502 get_position (const char **strv,
1507 for (i = 0; strv[i] != NULL; i++) {
1508 if (!tp_strdiff (strv[i], str))
1516 compare_separator_and_groups (gboolean is_separator_a,
1517 gboolean is_separator_b,
1518 const gchar *name_a,
1519 const gchar *name_b,
1520 EmpathyContact *contact_a,
1521 EmpathyContact *contact_b,
1522 gboolean fake_group_a,
1523 gboolean fake_group_b)
1525 /* these two lists are the sorted list of fake groups to include at the
1526 * top and bottom of the roster */
1527 const char *top_groups[] = {
1528 EMPATHY_CONTACT_LIST_STORE_FAVORITE,
1532 const char *bottom_groups[] = {
1533 EMPATHY_CONTACT_LIST_STORE_UNGROUPED,
1537 if (is_separator_a || is_separator_b) {
1538 /* We have at least one separator */
1539 if (is_separator_a) {
1541 } else if (is_separator_b) {
1546 /* One group and one contact */
1547 if (!contact_a && contact_b) {
1549 } else if (contact_a && !contact_b) {
1551 } else if (!contact_a && !contact_b) {
1552 gboolean a_in_top, b_in_top, a_in_bottom, b_in_bottom;
1554 a_in_top = fake_group_a &&
1555 tp_strv_contains (top_groups, name_a);
1556 b_in_top = fake_group_b &&
1557 tp_strv_contains (top_groups, name_b);
1558 a_in_bottom = fake_group_a &&
1559 tp_strv_contains (bottom_groups, name_a);
1560 b_in_bottom = fake_group_b &&
1561 tp_strv_contains (bottom_groups, name_b);
1563 if (a_in_top && b_in_top) {
1564 /* compare positions */
1565 return CLAMP (get_position (top_groups, name_a) -
1566 get_position (top_groups, name_b),
1568 } else if (a_in_bottom && b_in_bottom) {
1569 /* compare positions */
1570 return CLAMP (get_position (bottom_groups, name_a) -
1571 get_position (bottom_groups, name_b),
1573 } else if (a_in_top || b_in_bottom) {
1575 } else if (b_in_top || a_in_bottom) {
1578 return g_utf8_collate (name_a, name_b);
1582 /* Two contacts, ordering depends of the sorting policy */
1587 contact_list_store_contact_sort (EmpathyContact *contact_a,
1588 EmpathyContact *contact_b)
1590 TpAccount *account_a, *account_b;
1593 g_return_val_if_fail (contact_a != NULL || contact_b != NULL, 0);
1596 ret_val = g_utf8_collate (empathy_contact_get_alias (contact_a),
1597 empathy_contact_get_alias (contact_b));
1603 ret_val = g_utf8_collate (empathy_contact_get_id (contact_a),
1604 empathy_contact_get_id (contact_b));
1609 account_a = empathy_contact_get_account (contact_a);
1610 account_b = empathy_contact_get_account (contact_b);
1613 ret_val = strcmp (tp_account_get_protocol (account_a),
1614 tp_account_get_protocol (account_b));
1620 ret_val = strcmp (tp_proxy_get_object_path (account_a),
1621 tp_proxy_get_object_path (account_b));
1628 contact_list_store_state_sort_func (GtkTreeModel *model,
1629 GtkTreeIter *iter_a,
1630 GtkTreeIter *iter_b,
1634 gchar *name_a, *name_b;
1635 gboolean is_separator_a, is_separator_b;
1636 EmpathyContact *contact_a, *contact_b;
1637 gboolean fake_group_a, fake_group_b;
1639 gtk_tree_model_get (model, iter_a,
1640 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1641 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1642 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1643 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
1645 gtk_tree_model_get (model, iter_b,
1646 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1647 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1648 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1649 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
1652 if (contact_a == NULL || contact_b == NULL) {
1653 ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1654 name_a, name_b, contact_a, contact_b, fake_group_a, fake_group_b);
1658 /* If we managed to get this far, we can start looking at
1661 ret_val = -tp_connection_presence_type_cmp_availability (
1662 empathy_contact_get_presence (EMPATHY_CONTACT (contact_a)),
1663 empathy_contact_get_presence (EMPATHY_CONTACT (contact_b)));
1666 /* Fallback: compare by name et al. */
1667 ret_val = contact_list_store_contact_sort (contact_a, contact_b);
1675 g_object_unref (contact_a);
1679 g_object_unref (contact_b);
1686 contact_list_store_name_sort_func (GtkTreeModel *model,
1687 GtkTreeIter *iter_a,
1688 GtkTreeIter *iter_b,
1691 gchar *name_a, *name_b;
1692 EmpathyContact *contact_a, *contact_b;
1693 gboolean is_separator_a = FALSE, is_separator_b = FALSE;
1695 gboolean fake_group_a, fake_group_b;
1697 gtk_tree_model_get (model, iter_a,
1698 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1699 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1700 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1701 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_a,
1703 gtk_tree_model_get (model, iter_b,
1704 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1705 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1706 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1707 EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake_group_b,
1710 if (contact_a == NULL || contact_b == NULL)
1711 ret_val = compare_separator_and_groups (is_separator_a, is_separator_b,
1712 name_a, name_b, contact_a, contact_b, fake_group_a, fake_group_b);
1714 ret_val = contact_list_store_contact_sort (contact_a, contact_b);
1717 g_object_unref (contact_a);
1721 g_object_unref (contact_b);
1728 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1733 EmpathyContact *contact;
1735 gtk_tree_model_get (model, iter,
1736 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1739 if (contact == fc->contact) {
1741 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1745 g_object_unref (contact);
1752 contact_list_store_find_contact (EmpathyContactListStore *store,
1753 EmpathyContact *contact)
1755 GtkTreeModel *model;
1759 memset (&fc, 0, sizeof (fc));
1761 fc.contact = contact;
1763 model = GTK_TREE_MODEL (store);
1764 gtk_tree_model_foreach (model,
1765 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1776 contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
1779 EmpathyContactListStore *store)
1781 EmpathyContactListStorePriv *priv;
1782 gboolean show_avatar = FALSE;
1783 EmpathyContact *contact;
1784 GdkPixbuf *pixbuf_status;
1786 priv = GET_PRIV (store);
1788 if (priv->show_avatars && !priv->is_compact) {
1792 gtk_tree_model_get (model, iter,
1793 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1796 if (contact == NULL){
1799 /* get icon from hash_table */
1800 pixbuf_status = contact_list_store_get_contact_status_icon (store, contact);
1802 gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1803 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, pixbuf_status,
1804 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1805 EMPATHY_CONTACT_LIST_STORE_COL_COMPACT, priv->is_compact,
1812 contact_list_store_get_contact_status_icon_with_icon_name (
1813 EmpathyContactListStore *store,
1814 EmpathyContact *contact,
1815 const gchar *status_icon_name)
1817 GdkPixbuf *pixbuf_status = NULL;
1818 EmpathyContactListStorePriv *priv;
1819 const gchar *protocol_name = NULL;
1820 gchar *icon_name = NULL;
1822 priv = GET_PRIV (store);
1824 if (priv->show_protocols) {
1825 protocol_name = empathy_protocol_name_for_contact (contact);
1826 icon_name = g_strdup_printf ("%s-%s", status_icon_name, protocol_name);
1828 icon_name = g_strdup_printf ("%s", status_icon_name);
1830 pixbuf_status = g_hash_table_lookup (priv->status_icons, icon_name);
1831 if (pixbuf_status == NULL) {
1832 pixbuf_status = empathy_pixbuf_contact_status_icon_with_icon_name (contact,
1834 priv->show_protocols);
1835 if (pixbuf_status != NULL) {
1836 g_hash_table_insert (priv->status_icons,
1837 g_strdup (icon_name),
1843 return pixbuf_status;
1847 contact_list_store_get_contact_status_icon (EmpathyContactListStore *store,
1848 EmpathyContact *contact)
1850 GdkPixbuf *pixbuf_status = NULL;
1851 EmpathyContactListStorePriv *priv;
1852 const gchar *status_icon_name = NULL;
1853 gboolean composing = FALSE;
1855 priv = GET_PRIV (store);
1857 if (EMPATHY_IS_TP_CHAT (priv->list)) {
1858 if (empathy_tp_chat_get_chat_state (EMPATHY_TP_CHAT (priv->list),
1860 TP_CHANNEL_CHAT_STATE_COMPOSING)
1865 status_icon_name = EMPATHY_IMAGE_TYPING;
1867 status_icon_name = empathy_icon_name_for_contact (contact);
1870 if (status_icon_name == NULL)
1873 pixbuf_status = contact_list_store_get_contact_status_icon_with_icon_name (
1878 return pixbuf_status;