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>
33 #include <telepathy-glib/util.h>
35 #include <libempathy/empathy-utils.h>
36 #include <libempathy/empathy-enum-types.h>
37 #include <libempathy/empathy-contact-manager.h>
39 #include "empathy-contact-list-store.h"
40 #include "empathy-ui-utils.h"
41 #include "empathy-gtk-enum-types.h"
43 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
44 #include <libempathy/empathy-debug.h>
46 /* Active users are those which have recently changed state
47 * (e.g. online, offline or from normal to a busy state).
50 /* Time in seconds user is shown as active */
51 #define ACTIVE_USER_SHOW_TIME 7
53 /* Time in seconds after connecting which we wait before active users are enabled */
54 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5
56 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListStore)
58 EmpathyContactList *list;
59 gboolean show_offline;
60 gboolean show_avatars;
64 EmpathyContactListStoreSort sort_criterium;
67 } EmpathyContactListStorePriv;
76 EmpathyContact *contact;
82 EmpathyContactListStore *store;
83 EmpathyContact *contact;
87 static void contact_list_store_finalize (GObject *object);
88 static void contact_list_store_get_property (GObject *object,
92 static void contact_list_store_set_property (GObject *object,
96 static void contact_list_store_setup (EmpathyContactListStore *store);
97 static gboolean contact_list_store_inibit_active_cb (EmpathyContactListStore *store);
98 static void contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
99 EmpathyContact *contact,
100 EmpathyContact *actor,
104 EmpathyContactListStore *store);
105 static void contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
106 EmpathyContact *contact,
109 EmpathyContactListStore *store);
110 static void contact_list_store_add_contact (EmpathyContactListStore *store,
111 EmpathyContact *contact);
112 static void contact_list_store_remove_contact (EmpathyContactListStore *store,
113 EmpathyContact *contact);
114 static void contact_list_store_contact_update (EmpathyContactListStore *store,
115 EmpathyContact *contact);
116 static void contact_list_store_contact_updated_cb (EmpathyContact *contact,
118 EmpathyContactListStore *store);
119 static void contact_list_store_contact_set_active (EmpathyContactListStore *store,
120 EmpathyContact *contact,
122 gboolean set_changed);
123 static ShowActiveData * contact_list_store_contact_active_new (EmpathyContactListStore *store,
124 EmpathyContact *contact,
126 static void contact_list_store_contact_active_free (ShowActiveData *data);
127 static gboolean contact_list_store_contact_active_cb (ShowActiveData *data);
128 static gboolean contact_list_store_get_group_foreach (GtkTreeModel *model,
132 static void contact_list_store_get_group (EmpathyContactListStore *store,
134 GtkTreeIter *iter_group_to_set,
135 GtkTreeIter *iter_separator_to_set,
137 static gint contact_list_store_state_sort_func (GtkTreeModel *model,
141 static gint contact_list_store_name_sort_func (GtkTreeModel *model,
145 static gboolean contact_list_store_find_contact_foreach (GtkTreeModel *model,
149 static GList * contact_list_store_find_contact (EmpathyContactListStore *store,
150 EmpathyContact *contact);
151 static gboolean contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
154 EmpathyContactListStore *store);
166 G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
169 contact_list_store_iface_setup (gpointer user_data)
171 EmpathyContactListStore *store = user_data;
172 EmpathyContactListStorePriv *priv = GET_PRIV (store);
175 /* Signal connection. */
176 g_signal_connect (priv->list,
178 G_CALLBACK (contact_list_store_members_changed_cb),
180 g_signal_connect (priv->list,
182 G_CALLBACK (contact_list_store_groups_changed_cb),
185 /* Add contacts already created. */
186 contacts = empathy_contact_list_get_members (priv->list);
187 for (l = contacts; l; l = l->next) {
188 contact_list_store_members_changed_cb (priv->list, l->data,
193 g_object_unref (l->data);
195 g_list_free (contacts);
197 priv->setup_idle_id = 0;
203 contact_list_store_set_contact_list (EmpathyContactListStore *store,
204 EmpathyContactList *list_iface)
206 EmpathyContactListStorePriv *priv = GET_PRIV (store);
208 priv->list = g_object_ref (list_iface);
210 /* Let a chance to have all properties set before populating */
211 priv->setup_idle_id = g_idle_add (contact_list_store_iface_setup, store);
215 empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
217 GObjectClass *object_class = G_OBJECT_CLASS (klass);
219 object_class->finalize = contact_list_store_finalize;
220 object_class->get_property = contact_list_store_get_property;
221 object_class->set_property = contact_list_store_set_property;
223 g_object_class_install_property (object_class,
225 g_param_spec_object ("contact-list",
226 "The contact list iface",
227 "The contact list iface",
228 EMPATHY_TYPE_CONTACT_LIST,
229 G_PARAM_CONSTRUCT_ONLY |
231 g_object_class_install_property (object_class,
233 g_param_spec_boolean ("show-offline",
235 "Whether contact list should display "
239 g_object_class_install_property (object_class,
241 g_param_spec_boolean ("show-avatars",
243 "Whether contact list should display "
244 "avatars for contacts",
247 g_object_class_install_property (object_class,
249 g_param_spec_boolean ("show-groups",
251 "Whether contact list should display "
255 g_object_class_install_property (object_class,
257 g_param_spec_boolean ("is-compact",
259 "Whether the contact list is in compact mode or not",
263 g_object_class_install_property (object_class,
265 g_param_spec_enum ("sort-criterium",
267 "The sort criterium to use for sorting the contact list",
268 EMPATHY_TYPE_CONTACT_LIST_STORE_SORT,
269 EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
272 g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
276 empathy_contact_list_store_init (EmpathyContactListStore *store)
278 EmpathyContactListStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (store,
279 EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv);
282 priv->show_avatars = TRUE;
283 priv->show_groups = TRUE;
284 priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
285 (GSourceFunc) contact_list_store_inibit_active_cb,
287 contact_list_store_setup (store);
291 contact_list_store_finalize (GObject *object)
293 EmpathyContactListStorePriv *priv = GET_PRIV (object);
296 contacts = empathy_contact_list_get_members (priv->list);
297 for (l = contacts; l; l = l->next) {
298 g_signal_handlers_disconnect_by_func (l->data,
299 G_CALLBACK (contact_list_store_contact_updated_cb),
302 g_object_unref (l->data);
304 g_list_free (contacts);
306 g_signal_handlers_disconnect_by_func (priv->list,
307 G_CALLBACK (contact_list_store_members_changed_cb),
309 g_signal_handlers_disconnect_by_func (priv->list,
310 G_CALLBACK (contact_list_store_groups_changed_cb),
312 g_object_unref (priv->list);
314 if (priv->inhibit_active) {
315 g_source_remove (priv->inhibit_active);
318 if (priv->setup_idle_id != 0) {
319 g_source_remove (priv->setup_idle_id);
322 G_OBJECT_CLASS (empathy_contact_list_store_parent_class)->finalize (object);
326 contact_list_store_get_property (GObject *object,
331 EmpathyContactListStorePriv *priv;
333 priv = GET_PRIV (object);
336 case PROP_CONTACT_LIST:
337 g_value_set_object (value, priv->list);
339 case PROP_SHOW_OFFLINE:
340 g_value_set_boolean (value, priv->show_offline);
342 case PROP_SHOW_AVATARS:
343 g_value_set_boolean (value, priv->show_avatars);
345 case PROP_SHOW_GROUPS:
346 g_value_set_boolean (value, priv->show_groups);
348 case PROP_IS_COMPACT:
349 g_value_set_boolean (value, priv->is_compact);
351 case PROP_SORT_CRITERIUM:
352 g_value_set_enum (value, priv->sort_criterium);
355 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
361 contact_list_store_set_property (GObject *object,
366 EmpathyContactListStorePriv *priv;
368 priv = GET_PRIV (object);
371 case PROP_CONTACT_LIST:
372 contact_list_store_set_contact_list (EMPATHY_CONTACT_LIST_STORE (object),
373 g_value_get_object (value));
375 case PROP_SHOW_OFFLINE:
376 empathy_contact_list_store_set_show_offline (EMPATHY_CONTACT_LIST_STORE (object),
377 g_value_get_boolean (value));
379 case PROP_SHOW_AVATARS:
380 empathy_contact_list_store_set_show_avatars (EMPATHY_CONTACT_LIST_STORE (object),
381 g_value_get_boolean (value));
383 case PROP_SHOW_GROUPS:
384 empathy_contact_list_store_set_show_groups (EMPATHY_CONTACT_LIST_STORE (object),
385 g_value_get_boolean (value));
387 case PROP_IS_COMPACT:
388 empathy_contact_list_store_set_is_compact (EMPATHY_CONTACT_LIST_STORE (object),
389 g_value_get_boolean (value));
391 case PROP_SORT_CRITERIUM:
392 empathy_contact_list_store_set_sort_criterium (EMPATHY_CONTACT_LIST_STORE (object),
393 g_value_get_enum (value));
396 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
401 EmpathyContactListStore *
402 empathy_contact_list_store_new (EmpathyContactList *list_iface)
404 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
406 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_STORE,
407 "contact-list", list_iface,
412 empathy_contact_list_store_get_list_iface (EmpathyContactListStore *store)
414 EmpathyContactListStorePriv *priv;
416 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
418 priv = GET_PRIV (store);
424 empathy_contact_list_store_get_show_offline (EmpathyContactListStore *store)
426 EmpathyContactListStorePriv *priv;
428 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
430 priv = GET_PRIV (store);
432 return priv->show_offline;
436 empathy_contact_list_store_set_show_offline (EmpathyContactListStore *store,
437 gboolean show_offline)
439 EmpathyContactListStorePriv *priv;
441 gboolean show_active;
443 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
445 priv = GET_PRIV (store);
447 priv->show_offline = show_offline;
448 show_active = priv->show_active;
450 /* Disable temporarily. */
451 priv->show_active = FALSE;
453 contacts = empathy_contact_list_get_members (priv->list);
454 for (l = contacts; l; l = l->next) {
455 contact_list_store_contact_update (store, l->data);
457 g_object_unref (l->data);
459 g_list_free (contacts);
461 /* Restore to original setting. */
462 priv->show_active = show_active;
464 g_object_notify (G_OBJECT (store), "show-offline");
468 empathy_contact_list_store_get_show_avatars (EmpathyContactListStore *store)
470 EmpathyContactListStorePriv *priv;
472 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
474 priv = GET_PRIV (store);
476 return priv->show_avatars;
480 empathy_contact_list_store_set_show_avatars (EmpathyContactListStore *store,
481 gboolean show_avatars)
483 EmpathyContactListStorePriv *priv;
486 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
488 priv = GET_PRIV (store);
490 priv->show_avatars = show_avatars;
492 model = GTK_TREE_MODEL (store);
494 gtk_tree_model_foreach (model,
495 (GtkTreeModelForeachFunc)
496 contact_list_store_update_list_mode_foreach,
499 g_object_notify (G_OBJECT (store), "show-avatars");
503 empathy_contact_list_store_get_show_groups (EmpathyContactListStore *store)
505 EmpathyContactListStorePriv *priv;
507 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
509 priv = GET_PRIV (store);
511 return priv->show_groups;
515 empathy_contact_list_store_set_show_groups (EmpathyContactListStore *store,
516 gboolean show_groups)
518 EmpathyContactListStorePriv *priv;
521 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
523 priv = GET_PRIV (store);
525 if (priv->show_groups == show_groups) {
529 priv->show_groups = show_groups;
531 /* Remove all contacts and add them back, not optimized but that's the
533 gtk_tree_store_clear (GTK_TREE_STORE (store));
534 contacts = empathy_contact_list_get_members (priv->list);
535 for (l = contacts; l; l = l->next) {
536 contact_list_store_members_changed_cb (priv->list, l->data,
541 g_object_unref (l->data);
543 g_list_free (contacts);
545 g_object_notify (G_OBJECT (store), "show-groups");
549 empathy_contact_list_store_get_is_compact (EmpathyContactListStore *store)
551 EmpathyContactListStorePriv *priv;
553 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
555 priv = GET_PRIV (store);
557 return priv->is_compact;
561 empathy_contact_list_store_set_is_compact (EmpathyContactListStore *store,
564 EmpathyContactListStorePriv *priv;
567 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
569 priv = GET_PRIV (store);
571 priv->is_compact = is_compact;
573 model = GTK_TREE_MODEL (store);
575 gtk_tree_model_foreach (model,
576 (GtkTreeModelForeachFunc)
577 contact_list_store_update_list_mode_foreach,
580 g_object_notify (G_OBJECT (store), "is-compact");
583 EmpathyContactListStoreSort
584 empathy_contact_list_store_get_sort_criterium (EmpathyContactListStore *store)
586 EmpathyContactListStorePriv *priv;
588 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), 0);
590 priv = GET_PRIV (store);
592 return priv->sort_criterium;
596 empathy_contact_list_store_set_sort_criterium (EmpathyContactListStore *store,
597 EmpathyContactListStoreSort sort_criterium)
599 EmpathyContactListStorePriv *priv;
601 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
603 priv = GET_PRIV (store);
605 priv->sort_criterium = sort_criterium;
607 switch (sort_criterium) {
608 case EMPATHY_CONTACT_LIST_STORE_SORT_STATE:
609 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
610 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
614 case EMPATHY_CONTACT_LIST_STORE_SORT_NAME:
615 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
616 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
621 g_object_notify (G_OBJECT (store), "sort-criterium");
625 empathy_contact_list_store_row_separator_func (GtkTreeModel *model,
629 gboolean is_separator = FALSE;
631 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
633 gtk_tree_model_get (model, iter,
634 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
641 empathy_contact_list_store_get_parent_group (GtkTreeModel *model,
643 gboolean *path_is_group)
645 GtkTreeIter parent_iter, iter;
649 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
652 *path_is_group = FALSE;
655 if (!gtk_tree_model_get_iter (model, &iter, path)) {
659 gtk_tree_model_get (model, &iter,
660 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
661 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
668 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
674 gtk_tree_model_get (model, &iter,
675 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
676 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
685 *path_is_group = TRUE;
692 empathy_contact_list_store_search_equal_func (GtkTreeModel *model,
696 gpointer search_data)
698 gchar *name, *name_folded;
702 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
708 gtk_tree_model_get (model, iter,
709 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
716 name_folded = g_utf8_casefold (name, -1);
717 key_folded = g_utf8_casefold (key, -1);
719 if (name_folded && key_folded &&
720 strstr (name_folded, key_folded)) {
727 g_free (name_folded);
734 contact_list_store_setup (EmpathyContactListStore *store)
736 EmpathyContactListStorePriv *priv;
738 G_TYPE_STRING, /* Status icon-name */
739 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
740 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
741 G_TYPE_STRING, /* Name */
742 G_TYPE_STRING, /* Status string */
743 G_TYPE_BOOLEAN, /* Show status */
744 EMPATHY_TYPE_CONTACT, /* Contact type */
745 G_TYPE_BOOLEAN, /* Is group */
746 G_TYPE_BOOLEAN, /* Is active */
747 G_TYPE_BOOLEAN, /* Is online */
748 G_TYPE_BOOLEAN, /* Is separator */
749 G_TYPE_BOOLEAN, /* Can make audio calls */
750 G_TYPE_BOOLEAN, /* Can make video calls */
751 EMPATHY_TYPE_CONTACT_LIST_FLAGS, /* Flags */
754 priv = GET_PRIV (store);
756 gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
757 EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
761 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
762 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
763 contact_list_store_name_sort_func,
765 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
766 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
767 contact_list_store_state_sort_func,
770 priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
771 empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
775 contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
777 EmpathyContactListStorePriv *priv;
779 priv = GET_PRIV (store);
781 priv->show_active = TRUE;
782 priv->inhibit_active = 0;
788 contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
789 EmpathyContact *contact,
790 EmpathyContact *actor,
794 EmpathyContactListStore *store)
796 EmpathyContactListStorePriv *priv;
798 priv = GET_PRIV (store);
800 DEBUG ("Contact %s (%d) %s",
801 empathy_contact_get_id (contact),
802 empathy_contact_get_handle (contact),
803 is_member ? "added" : "removed");
806 g_signal_connect (contact, "notify::presence",
807 G_CALLBACK (contact_list_store_contact_updated_cb),
809 g_signal_connect (contact, "notify::presence-message",
810 G_CALLBACK (contact_list_store_contact_updated_cb),
812 g_signal_connect (contact, "notify::name",
813 G_CALLBACK (contact_list_store_contact_updated_cb),
815 g_signal_connect (contact, "notify::avatar",
816 G_CALLBACK (contact_list_store_contact_updated_cb),
818 g_signal_connect (contact, "notify::capabilities",
819 G_CALLBACK (contact_list_store_contact_updated_cb),
822 contact_list_store_add_contact (store, contact);
824 g_signal_handlers_disconnect_by_func (contact,
825 G_CALLBACK (contact_list_store_contact_updated_cb),
828 contact_list_store_remove_contact (store, contact);
833 contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
834 EmpathyContact *contact,
837 EmpathyContactListStore *store)
839 EmpathyContactListStorePriv *priv;
840 gboolean show_active;
842 priv = GET_PRIV (store);
844 DEBUG ("Updating groups for contact %s (%d)",
845 empathy_contact_get_id (contact),
846 empathy_contact_get_handle (contact));
848 /* We do this to make sure the groups are correct, if not, we
849 * would have to check the groups already set up for each
850 * contact and then see what has been updated.
852 show_active = priv->show_active;
853 priv->show_active = FALSE;
854 contact_list_store_remove_contact (store, contact);
855 contact_list_store_add_contact (store, contact);
856 priv->show_active = show_active;
860 contact_list_store_add_contact (EmpathyContactListStore *store,
861 EmpathyContact *contact)
863 EmpathyContactListStorePriv *priv;
865 GList *groups = NULL, *l;
866 TpConnection *connection;
867 EmpathyContactListFlags flags = 0;
869 priv = GET_PRIV (store);
871 if (EMP_STR_EMPTY (empathy_contact_get_name (contact)) ||
872 (!priv->show_offline && !empathy_contact_is_online (contact))) {
876 if (priv->show_groups) {
877 groups = empathy_contact_list_get_groups (priv->list, contact);
880 connection = empathy_contact_get_connection (contact);
881 if (EMPATHY_IS_CONTACT_MANAGER (priv->list)) {
882 flags = empathy_contact_manager_get_flags_for_connection (
883 EMPATHY_CONTACT_MANAGER (priv->list), connection);
885 /* If no groups just add it at the top level. */
888 GtkTreeModel *model = GTK_TREE_MODEL (store);
890 if (gtk_tree_model_get_iter_first (model, &iter)) do {
893 gtk_tree_model_get (model, &iter,
894 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &c,
903 } while (gtk_tree_model_iter_next (model, &iter));
905 gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
906 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
907 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
908 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
909 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
910 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
911 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
912 empathy_contact_get_capabilities (contact) &
913 EMPATHY_CAPABILITIES_AUDIO,
914 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
915 empathy_contact_get_capabilities (contact) &
916 EMPATHY_CAPABILITIES_VIDEO,
917 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
921 /* Else add to each group. */
922 for (l = groups; l; l = l->next) {
923 GtkTreeIter iter_group;
925 contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL);
927 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
929 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
930 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
931 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
932 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
933 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
934 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
935 empathy_contact_get_capabilities (contact) &
936 EMPATHY_CAPABILITIES_AUDIO,
937 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
938 empathy_contact_get_capabilities (contact) &
939 EMPATHY_CAPABILITIES_VIDEO,
940 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
944 g_list_free (groups);
946 contact_list_store_contact_update (store, contact);
951 contact_list_store_remove_contact (EmpathyContactListStore *store,
952 EmpathyContact *contact)
954 EmpathyContactListStorePriv *priv;
958 priv = GET_PRIV (store);
960 iters = contact_list_store_find_contact (store, contact);
966 model = GTK_TREE_MODEL (store);
968 for (l = iters; l; l = l->next) {
971 /* NOTE: it is only <= 2 here because we have
972 * separators after the group name, otherwise it
975 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
976 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
977 gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
979 gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
983 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
988 contact_list_store_contact_update (EmpathyContactListStore *store,
989 EmpathyContact *contact)
991 EmpathyContactListStorePriv *priv;
992 ShowActiveData *data;
996 gboolean should_be_in_list;
997 gboolean was_online = TRUE;
998 gboolean now_online = FALSE;
999 gboolean set_model = FALSE;
1000 gboolean do_remove = FALSE;
1001 gboolean do_set_active = FALSE;
1002 gboolean do_set_refresh = FALSE;
1003 gboolean show_avatar = FALSE;
1004 GdkPixbuf *pixbuf_avatar;
1006 priv = GET_PRIV (store);
1008 model = GTK_TREE_MODEL (store);
1010 iters = contact_list_store_find_contact (store, contact);
1017 /* Get online state now. */
1018 now_online = empathy_contact_is_online (contact);
1020 if (priv->show_offline || now_online) {
1021 should_be_in_list = TRUE;
1023 should_be_in_list = FALSE;
1026 if (!in_list && !should_be_in_list) {
1027 /* Nothing to do. */
1028 DEBUG ("Contact:'%s' in list:NO, should be:NO",
1029 empathy_contact_get_name (contact));
1031 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1032 g_list_free (iters);
1035 else if (in_list && !should_be_in_list) {
1036 DEBUG ("Contact:'%s' in list:YES, should be:NO",
1037 empathy_contact_get_name (contact));
1039 if (priv->show_active) {
1041 do_set_active = TRUE;
1042 do_set_refresh = TRUE;
1045 DEBUG ("Remove item (after timeout)");
1047 DEBUG ("Remove item (now)!");
1048 contact_list_store_remove_contact (store, contact);
1051 else if (!in_list && should_be_in_list) {
1052 DEBUG ("Contact:'%s' in list:NO, should be:YES",
1053 empathy_contact_get_name (contact));
1055 contact_list_store_add_contact (store, contact);
1057 if (priv->show_active) {
1058 do_set_active = TRUE;
1060 DEBUG ("Set active (contact added)");
1063 DEBUG ("Contact:'%s' in list:YES, should be:YES",
1064 empathy_contact_get_name (contact));
1066 /* Get online state before. */
1067 if (iters && g_list_length (iters) > 0) {
1068 gtk_tree_model_get (model, iters->data,
1069 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, &was_online,
1073 /* Is this really an update or an online/offline. */
1074 if (priv->show_active) {
1075 if (was_online != now_online) {
1076 do_set_active = TRUE;
1077 do_set_refresh = TRUE;
1079 DEBUG ("Set active (contact updated %s)",
1080 was_online ? "online -> offline" :
1081 "offline -> online");
1083 /* Was TRUE for presence updates. */
1084 /* do_set_active = FALSE; */
1085 do_set_refresh = TRUE;
1087 DEBUG ("Set active (contact updated)");
1094 if (priv->show_avatars && !priv->is_compact) {
1097 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1098 for (l = iters; l && set_model; l = l->next) {
1099 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1100 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, empathy_icon_name_for_contact (contact),
1101 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
1102 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1103 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
1104 EMPATHY_CONTACT_LIST_STORE_COL_STATUS, empathy_contact_get_status (contact),
1105 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,
1106 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1107 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
1108 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1109 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1110 empathy_contact_get_capabilities (contact) &
1111 EMPATHY_CAPABILITIES_AUDIO,
1112 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1113 empathy_contact_get_capabilities (contact) &
1114 EMPATHY_CAPABILITIES_VIDEO,
1118 if (pixbuf_avatar) {
1119 g_object_unref (pixbuf_avatar);
1122 if (priv->show_active && do_set_active) {
1123 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1125 if (do_set_active) {
1126 data = contact_list_store_contact_active_new (store, contact, do_remove);
1127 g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
1128 (GSourceFunc) contact_list_store_contact_active_cb,
1133 /* FIXME: when someone goes online then offline quickly, the
1134 * first timeout sets the user to be inactive and the second
1135 * timeout removes the user from the contact list, really we
1136 * should remove the first timeout.
1138 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1139 g_list_free (iters);
1143 contact_list_store_contact_updated_cb (EmpathyContact *contact,
1145 EmpathyContactListStore *store)
1147 DEBUG ("Contact:'%s' updated, checking roster is in sync...",
1148 empathy_contact_get_name (contact));
1150 contact_list_store_contact_update (store, contact);
1154 contact_list_store_contact_set_active (EmpathyContactListStore *store,
1155 EmpathyContact *contact,
1157 gboolean set_changed)
1159 EmpathyContactListStorePriv *priv;
1160 GtkTreeModel *model;
1163 priv = GET_PRIV (store);
1164 model = GTK_TREE_MODEL (store);
1166 iters = contact_list_store_find_contact (store, contact);
1167 for (l = iters; l; l = l->next) {
1170 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1171 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, active,
1174 DEBUG ("Set item %s", active ? "active" : "inactive");
1177 path = gtk_tree_model_get_path (model, l->data);
1178 gtk_tree_model_row_changed (model, path, l->data);
1179 gtk_tree_path_free (path);
1183 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1184 g_list_free (iters);
1188 static ShowActiveData *
1189 contact_list_store_contact_active_new (EmpathyContactListStore *store,
1190 EmpathyContact *contact,
1193 ShowActiveData *data;
1195 DEBUG ("Contact:'%s' now active, and %s be removed",
1196 empathy_contact_get_name (contact),
1197 remove ? "WILL" : "WILL NOT");
1199 data = g_slice_new0 (ShowActiveData);
1201 data->store = g_object_ref (store);
1202 data->contact = g_object_ref (contact);
1203 data->remove = remove;
1209 contact_list_store_contact_active_free (ShowActiveData *data)
1211 g_object_unref (data->contact);
1212 g_object_unref (data->store);
1214 g_slice_free (ShowActiveData, data);
1218 contact_list_store_contact_active_cb (ShowActiveData *data)
1220 EmpathyContactListStorePriv *priv;
1222 priv = GET_PRIV (data->store);
1225 !priv->show_offline &&
1226 !empathy_contact_is_online (data->contact)) {
1227 DEBUG ("Contact:'%s' active timeout, removing item",
1228 empathy_contact_get_name (data->contact));
1229 contact_list_store_remove_contact (data->store, data->contact);
1232 DEBUG ("Contact:'%s' no longer active",
1233 empathy_contact_get_name (data->contact));
1235 contact_list_store_contact_set_active (data->store,
1240 contact_list_store_contact_active_free (data);
1246 contact_list_store_get_group_foreach (GtkTreeModel *model,
1254 /* Groups are only at the top level. */
1255 if (gtk_tree_path_get_depth (path) != 1) {
1259 gtk_tree_model_get (model, iter,
1260 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &str,
1261 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1264 if (is_group && !tp_strdiff (str, fg->name)) {
1275 contact_list_store_get_group (EmpathyContactListStore *store,
1277 GtkTreeIter *iter_group_to_set,
1278 GtkTreeIter *iter_separator_to_set,
1281 EmpathyContactListStorePriv *priv;
1282 GtkTreeModel *model;
1283 GtkTreeIter iter_group;
1284 GtkTreeIter iter_separator;
1287 priv = GET_PRIV (store);
1289 memset (&fg, 0, sizeof (fg));
1293 model = GTK_TREE_MODEL (store);
1294 gtk_tree_model_foreach (model,
1295 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1303 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1304 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1305 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
1306 EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
1307 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
1308 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
1309 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1312 if (iter_group_to_set) {
1313 *iter_group_to_set = iter_group;
1316 gtk_tree_store_append (GTK_TREE_STORE (store),
1319 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1320 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, TRUE,
1323 if (iter_separator_to_set) {
1324 *iter_separator_to_set = iter_separator;
1331 if (iter_group_to_set) {
1332 *iter_group_to_set = fg.iter;
1335 iter_separator = fg.iter;
1337 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1338 gboolean is_separator;
1340 gtk_tree_model_get (model, &iter_separator,
1341 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1344 if (is_separator && iter_separator_to_set) {
1345 *iter_separator_to_set = iter_separator;
1352 contact_list_store_state_sort_func (GtkTreeModel *model,
1353 GtkTreeIter *iter_a,
1354 GtkTreeIter *iter_b,
1358 gchar *name_a, *name_b;
1359 gboolean is_separator_a, is_separator_b;
1360 EmpathyContact *contact_a, *contact_b;
1362 gtk_tree_model_get (model, iter_a,
1363 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1364 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1365 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1367 gtk_tree_model_get (model, iter_b,
1368 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1369 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1370 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1373 /* Separator or group? */
1374 if (is_separator_a || is_separator_b) {
1375 if (is_separator_a) {
1377 } else if (is_separator_b) {
1380 } else if (!contact_a && contact_b) {
1382 } else if (contact_a && !contact_b) {
1384 } else if (!contact_a && !contact_b) {
1386 ret_val = g_utf8_collate (name_a, name_b);
1393 /* If we managed to get this far, we can start looking at
1396 ret_val = -tp_connection_presence_type_cmp_availability (
1397 empathy_contact_get_presence (EMPATHY_CONTACT (contact_a)),
1398 empathy_contact_get_presence (EMPATHY_CONTACT (contact_b)));
1401 /* Fallback: compare by name */
1402 ret_val = g_utf8_collate (name_a, name_b);
1410 g_object_unref (contact_a);
1414 g_object_unref (contact_b);
1421 contact_list_store_name_sort_func (GtkTreeModel *model,
1422 GtkTreeIter *iter_a,
1423 GtkTreeIter *iter_b,
1426 gchar *name_a, *name_b;
1427 EmpathyContact *contact_a, *contact_b;
1428 gboolean is_separator_a, is_separator_b;
1431 gtk_tree_model_get (model, iter_a,
1432 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1433 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1434 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1436 gtk_tree_model_get (model, iter_b,
1437 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1438 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1439 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1442 /* If contact is NULL it means it's a group. */
1444 if (is_separator_a || is_separator_b) {
1445 if (is_separator_a) {
1447 } else if (is_separator_b) {
1450 } else if (!contact_a && contact_b) {
1452 } else if (contact_a && !contact_b) {
1455 ret_val = g_utf8_collate (name_a, name_b);
1462 g_object_unref (contact_a);
1466 g_object_unref (contact_b);
1473 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1478 EmpathyContact *contact;
1480 gtk_tree_model_get (model, iter,
1481 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1484 if (contact == fc->contact) {
1486 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1490 g_object_unref (contact);
1497 contact_list_store_find_contact (EmpathyContactListStore *store,
1498 EmpathyContact *contact)
1500 EmpathyContactListStorePriv *priv;
1501 GtkTreeModel *model;
1505 priv = GET_PRIV (store);
1507 memset (&fc, 0, sizeof (fc));
1509 fc.contact = contact;
1511 model = GTK_TREE_MODEL (store);
1512 gtk_tree_model_foreach (model,
1513 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1524 contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
1527 EmpathyContactListStore *store)
1529 EmpathyContactListStorePriv *priv;
1530 gboolean show_avatar = FALSE;
1532 priv = GET_PRIV (store);
1534 if (priv->show_avatars && !priv->is_compact) {
1538 gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1539 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1540 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,