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 gboolean dispose_has_run;
68 } EmpathyContactListStorePriv;
77 EmpathyContact *contact;
83 EmpathyContactListStore *store;
84 EmpathyContact *contact;
88 static void contact_list_store_dispose (GObject *object);
89 static void contact_list_store_get_property (GObject *object,
93 static void contact_list_store_set_property (GObject *object,
97 static void contact_list_store_setup (EmpathyContactListStore *store);
98 static gboolean contact_list_store_inibit_active_cb (EmpathyContactListStore *store);
99 static void contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
100 EmpathyContact *contact,
101 EmpathyContact *actor,
105 EmpathyContactListStore *store);
106 static void contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
107 EmpathyContact *old_contact,
108 EmpathyContact *new_contact,
111 EmpathyContactListStore *store);
112 static void contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
113 EmpathyContact *contact,
116 EmpathyContactListStore *store);
117 static void contact_list_store_add_contact (EmpathyContactListStore *store,
118 EmpathyContact *contact);
119 static void contact_list_store_remove_contact (EmpathyContactListStore *store,
120 EmpathyContact *contact);
121 static void contact_list_store_contact_update (EmpathyContactListStore *store,
122 EmpathyContact *contact);
123 static void contact_list_store_contact_updated_cb (EmpathyContact *contact,
125 EmpathyContactListStore *store);
126 static void contact_list_store_contact_set_active (EmpathyContactListStore *store,
127 EmpathyContact *contact,
129 gboolean set_changed);
130 static ShowActiveData * contact_list_store_contact_active_new (EmpathyContactListStore *store,
131 EmpathyContact *contact,
133 static void contact_list_store_contact_active_free (ShowActiveData *data);
134 static gboolean contact_list_store_contact_active_cb (ShowActiveData *data);
135 static gboolean contact_list_store_get_group_foreach (GtkTreeModel *model,
139 static void contact_list_store_get_group (EmpathyContactListStore *store,
141 GtkTreeIter *iter_group_to_set,
142 GtkTreeIter *iter_separator_to_set,
144 static gint contact_list_store_state_sort_func (GtkTreeModel *model,
148 static gint contact_list_store_name_sort_func (GtkTreeModel *model,
152 static gboolean contact_list_store_find_contact_foreach (GtkTreeModel *model,
156 static GList * contact_list_store_find_contact (EmpathyContactListStore *store,
157 EmpathyContact *contact);
158 static gboolean contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
161 EmpathyContactListStore *store);
173 G_DEFINE_TYPE (EmpathyContactListStore, empathy_contact_list_store, GTK_TYPE_TREE_STORE);
176 contact_list_store_iface_setup (gpointer user_data)
178 EmpathyContactListStore *store = user_data;
179 EmpathyContactListStorePriv *priv = GET_PRIV (store);
182 /* Signal connection. */
183 g_signal_connect (priv->list,
185 G_CALLBACK (contact_list_store_member_renamed_cb),
187 g_signal_connect (priv->list,
189 G_CALLBACK (contact_list_store_members_changed_cb),
191 g_signal_connect (priv->list,
193 G_CALLBACK (contact_list_store_groups_changed_cb),
196 /* Add contacts already created. */
197 contacts = empathy_contact_list_get_members (priv->list);
198 for (l = contacts; l; l = l->next) {
199 contact_list_store_members_changed_cb (priv->list, l->data,
204 g_object_unref (l->data);
206 g_list_free (contacts);
208 priv->setup_idle_id = 0;
214 contact_list_store_set_contact_list (EmpathyContactListStore *store,
215 EmpathyContactList *list_iface)
217 EmpathyContactListStorePriv *priv = GET_PRIV (store);
219 priv->list = g_object_ref (list_iface);
221 /* Let a chance to have all properties set before populating */
222 priv->setup_idle_id = g_idle_add (contact_list_store_iface_setup, store);
226 empathy_contact_list_store_class_init (EmpathyContactListStoreClass *klass)
228 GObjectClass *object_class = G_OBJECT_CLASS (klass);
230 object_class->dispose = contact_list_store_dispose;
231 object_class->get_property = contact_list_store_get_property;
232 object_class->set_property = contact_list_store_set_property;
234 g_object_class_install_property (object_class,
236 g_param_spec_object ("contact-list",
237 "The contact list iface",
238 "The contact list iface",
239 EMPATHY_TYPE_CONTACT_LIST,
240 G_PARAM_CONSTRUCT_ONLY |
242 g_object_class_install_property (object_class,
244 g_param_spec_boolean ("show-offline",
246 "Whether contact list should display "
250 g_object_class_install_property (object_class,
252 g_param_spec_boolean ("show-avatars",
254 "Whether contact list should display "
255 "avatars for contacts",
258 g_object_class_install_property (object_class,
260 g_param_spec_boolean ("show-groups",
262 "Whether contact list should display "
266 g_object_class_install_property (object_class,
268 g_param_spec_boolean ("is-compact",
270 "Whether the contact list is in compact mode or not",
274 g_object_class_install_property (object_class,
276 g_param_spec_enum ("sort-criterium",
278 "The sort criterium to use for sorting the contact list",
279 EMPATHY_TYPE_CONTACT_LIST_STORE_SORT,
280 EMPATHY_CONTACT_LIST_STORE_SORT_NAME,
283 g_type_class_add_private (object_class, sizeof (EmpathyContactListStorePriv));
287 empathy_contact_list_store_init (EmpathyContactListStore *store)
289 EmpathyContactListStorePriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (store,
290 EMPATHY_TYPE_CONTACT_LIST_STORE, EmpathyContactListStorePriv);
293 priv->show_avatars = TRUE;
294 priv->show_groups = TRUE;
295 priv->inhibit_active = g_timeout_add_seconds (ACTIVE_USER_WAIT_TO_ENABLE_TIME,
296 (GSourceFunc) contact_list_store_inibit_active_cb,
298 contact_list_store_setup (store);
302 contact_list_store_dispose (GObject *object)
304 EmpathyContactListStorePriv *priv = GET_PRIV (object);
307 if (priv->dispose_has_run)
309 priv->dispose_has_run = TRUE;
311 contacts = empathy_contact_list_get_members (priv->list);
312 for (l = contacts; l; l = l->next) {
313 g_signal_handlers_disconnect_by_func (l->data,
314 G_CALLBACK (contact_list_store_contact_updated_cb),
317 g_object_unref (l->data);
319 g_list_free (contacts);
321 g_signal_handlers_disconnect_by_func (priv->list,
322 G_CALLBACK (contact_list_store_member_renamed_cb),
324 g_signal_handlers_disconnect_by_func (priv->list,
325 G_CALLBACK (contact_list_store_members_changed_cb),
327 g_signal_handlers_disconnect_by_func (priv->list,
328 G_CALLBACK (contact_list_store_groups_changed_cb),
330 g_object_unref (priv->list);
332 if (priv->inhibit_active) {
333 g_source_remove (priv->inhibit_active);
336 if (priv->setup_idle_id != 0) {
337 g_source_remove (priv->setup_idle_id);
340 G_OBJECT_CLASS (empathy_contact_list_store_parent_class)->dispose (object);
344 contact_list_store_get_property (GObject *object,
349 EmpathyContactListStorePriv *priv;
351 priv = GET_PRIV (object);
354 case PROP_CONTACT_LIST:
355 g_value_set_object (value, priv->list);
357 case PROP_SHOW_OFFLINE:
358 g_value_set_boolean (value, priv->show_offline);
360 case PROP_SHOW_AVATARS:
361 g_value_set_boolean (value, priv->show_avatars);
363 case PROP_SHOW_GROUPS:
364 g_value_set_boolean (value, priv->show_groups);
366 case PROP_IS_COMPACT:
367 g_value_set_boolean (value, priv->is_compact);
369 case PROP_SORT_CRITERIUM:
370 g_value_set_enum (value, priv->sort_criterium);
373 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
379 contact_list_store_set_property (GObject *object,
384 EmpathyContactListStorePriv *priv;
386 priv = GET_PRIV (object);
389 case PROP_CONTACT_LIST:
390 contact_list_store_set_contact_list (EMPATHY_CONTACT_LIST_STORE (object),
391 g_value_get_object (value));
393 case PROP_SHOW_OFFLINE:
394 empathy_contact_list_store_set_show_offline (EMPATHY_CONTACT_LIST_STORE (object),
395 g_value_get_boolean (value));
397 case PROP_SHOW_AVATARS:
398 empathy_contact_list_store_set_show_avatars (EMPATHY_CONTACT_LIST_STORE (object),
399 g_value_get_boolean (value));
401 case PROP_SHOW_GROUPS:
402 empathy_contact_list_store_set_show_groups (EMPATHY_CONTACT_LIST_STORE (object),
403 g_value_get_boolean (value));
405 case PROP_IS_COMPACT:
406 empathy_contact_list_store_set_is_compact (EMPATHY_CONTACT_LIST_STORE (object),
407 g_value_get_boolean (value));
409 case PROP_SORT_CRITERIUM:
410 empathy_contact_list_store_set_sort_criterium (EMPATHY_CONTACT_LIST_STORE (object),
411 g_value_get_enum (value));
414 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
419 EmpathyContactListStore *
420 empathy_contact_list_store_new (EmpathyContactList *list_iface)
422 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface), NULL);
424 return g_object_new (EMPATHY_TYPE_CONTACT_LIST_STORE,
425 "contact-list", list_iface,
430 empathy_contact_list_store_get_list_iface (EmpathyContactListStore *store)
432 EmpathyContactListStorePriv *priv;
434 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
436 priv = GET_PRIV (store);
442 empathy_contact_list_store_get_show_offline (EmpathyContactListStore *store)
444 EmpathyContactListStorePriv *priv;
446 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), FALSE);
448 priv = GET_PRIV (store);
450 return priv->show_offline;
454 empathy_contact_list_store_set_show_offline (EmpathyContactListStore *store,
455 gboolean show_offline)
457 EmpathyContactListStorePriv *priv;
459 gboolean show_active;
461 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
463 priv = GET_PRIV (store);
465 priv->show_offline = show_offline;
466 show_active = priv->show_active;
468 /* Disable temporarily. */
469 priv->show_active = FALSE;
471 contacts = empathy_contact_list_get_members (priv->list);
472 for (l = contacts; l; l = l->next) {
473 contact_list_store_contact_update (store, l->data);
475 g_object_unref (l->data);
477 g_list_free (contacts);
479 /* Restore to original setting. */
480 priv->show_active = show_active;
482 g_object_notify (G_OBJECT (store), "show-offline");
486 empathy_contact_list_store_get_show_avatars (EmpathyContactListStore *store)
488 EmpathyContactListStorePriv *priv;
490 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
492 priv = GET_PRIV (store);
494 return priv->show_avatars;
498 empathy_contact_list_store_set_show_avatars (EmpathyContactListStore *store,
499 gboolean show_avatars)
501 EmpathyContactListStorePriv *priv;
504 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
506 priv = GET_PRIV (store);
508 priv->show_avatars = show_avatars;
510 model = GTK_TREE_MODEL (store);
512 gtk_tree_model_foreach (model,
513 (GtkTreeModelForeachFunc)
514 contact_list_store_update_list_mode_foreach,
517 g_object_notify (G_OBJECT (store), "show-avatars");
521 empathy_contact_list_store_get_show_groups (EmpathyContactListStore *store)
523 EmpathyContactListStorePriv *priv;
525 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
527 priv = GET_PRIV (store);
529 return priv->show_groups;
533 empathy_contact_list_store_set_show_groups (EmpathyContactListStore *store,
534 gboolean show_groups)
536 EmpathyContactListStorePriv *priv;
539 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
541 priv = GET_PRIV (store);
543 if (priv->show_groups == show_groups) {
547 priv->show_groups = show_groups;
549 /* Remove all contacts and add them back, not optimized but that's the
551 gtk_tree_store_clear (GTK_TREE_STORE (store));
552 contacts = empathy_contact_list_get_members (priv->list);
553 for (l = contacts; l; l = l->next) {
554 contact_list_store_members_changed_cb (priv->list, l->data,
559 g_object_unref (l->data);
561 g_list_free (contacts);
563 g_object_notify (G_OBJECT (store), "show-groups");
567 empathy_contact_list_store_get_is_compact (EmpathyContactListStore *store)
569 EmpathyContactListStorePriv *priv;
571 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), TRUE);
573 priv = GET_PRIV (store);
575 return priv->is_compact;
579 empathy_contact_list_store_set_is_compact (EmpathyContactListStore *store,
582 EmpathyContactListStorePriv *priv;
585 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
587 priv = GET_PRIV (store);
589 priv->is_compact = is_compact;
591 model = GTK_TREE_MODEL (store);
593 gtk_tree_model_foreach (model,
594 (GtkTreeModelForeachFunc)
595 contact_list_store_update_list_mode_foreach,
598 g_object_notify (G_OBJECT (store), "is-compact");
601 EmpathyContactListStoreSort
602 empathy_contact_list_store_get_sort_criterium (EmpathyContactListStore *store)
604 EmpathyContactListStorePriv *priv;
606 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), 0);
608 priv = GET_PRIV (store);
610 return priv->sort_criterium;
614 empathy_contact_list_store_set_sort_criterium (EmpathyContactListStore *store,
615 EmpathyContactListStoreSort sort_criterium)
617 EmpathyContactListStorePriv *priv;
619 g_return_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store));
621 priv = GET_PRIV (store);
623 priv->sort_criterium = sort_criterium;
625 switch (sort_criterium) {
626 case EMPATHY_CONTACT_LIST_STORE_SORT_STATE:
627 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
628 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
632 case EMPATHY_CONTACT_LIST_STORE_SORT_NAME:
633 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
634 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
639 g_object_notify (G_OBJECT (store), "sort-criterium");
643 empathy_contact_list_store_row_separator_func (GtkTreeModel *model,
647 gboolean is_separator = FALSE;
649 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
651 gtk_tree_model_get (model, iter,
652 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
659 empathy_contact_list_store_get_parent_group (GtkTreeModel *model,
661 gboolean *path_is_group)
663 GtkTreeIter parent_iter, iter;
667 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
670 *path_is_group = FALSE;
673 if (!gtk_tree_model_get_iter (model, &iter, path)) {
677 gtk_tree_model_get (model, &iter,
678 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
679 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
686 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
692 gtk_tree_model_get (model, &iter,
693 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
694 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
703 *path_is_group = TRUE;
710 empathy_contact_list_store_search_equal_func (GtkTreeModel *model,
714 gpointer search_data)
716 gchar *name, *name_folded;
720 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), FALSE);
726 gtk_tree_model_get (model, iter,
727 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
734 name_folded = g_utf8_casefold (name, -1);
735 key_folded = g_utf8_casefold (key, -1);
737 if (name_folded && key_folded &&
738 strstr (name_folded, key_folded)) {
745 g_free (name_folded);
752 contact_list_store_setup (EmpathyContactListStore *store)
754 EmpathyContactListStorePriv *priv;
756 G_TYPE_STRING, /* Status icon-name */
757 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
758 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
759 G_TYPE_STRING, /* Name */
760 G_TYPE_STRING, /* Status string */
761 G_TYPE_BOOLEAN, /* Show status */
762 EMPATHY_TYPE_CONTACT, /* Contact type */
763 G_TYPE_BOOLEAN, /* Is group */
764 G_TYPE_BOOLEAN, /* Is active */
765 G_TYPE_BOOLEAN, /* Is online */
766 G_TYPE_BOOLEAN, /* Is separator */
767 G_TYPE_BOOLEAN, /* Can make audio calls */
768 G_TYPE_BOOLEAN, /* Can make video calls */
769 EMPATHY_TYPE_CONTACT_LIST_FLAGS, /* Flags */
772 priv = GET_PRIV (store);
774 gtk_tree_store_set_column_types (GTK_TREE_STORE (store),
775 EMPATHY_CONTACT_LIST_STORE_COL_COUNT,
779 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
780 EMPATHY_CONTACT_LIST_STORE_COL_NAME,
781 contact_list_store_name_sort_func,
783 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
784 EMPATHY_CONTACT_LIST_STORE_COL_STATUS,
785 contact_list_store_state_sort_func,
788 priv->sort_criterium = EMPATHY_CONTACT_LIST_STORE_SORT_NAME;
789 empathy_contact_list_store_set_sort_criterium (store, priv->sort_criterium);
793 contact_list_store_inibit_active_cb (EmpathyContactListStore *store)
795 EmpathyContactListStorePriv *priv;
797 priv = GET_PRIV (store);
799 priv->show_active = TRUE;
800 priv->inhibit_active = 0;
806 contact_list_store_add_contact_and_connect (EmpathyContactListStore *store, EmpathyContact *contact)
808 g_signal_connect (contact, "notify::presence",
809 G_CALLBACK (contact_list_store_contact_updated_cb),
811 g_signal_connect (contact, "notify::presence-message",
812 G_CALLBACK (contact_list_store_contact_updated_cb),
814 g_signal_connect (contact, "notify::name",
815 G_CALLBACK (contact_list_store_contact_updated_cb),
817 g_signal_connect (contact, "notify::avatar",
818 G_CALLBACK (contact_list_store_contact_updated_cb),
820 g_signal_connect (contact, "notify::capabilities",
821 G_CALLBACK (contact_list_store_contact_updated_cb),
824 contact_list_store_add_contact (store, contact);
828 contact_list_store_remove_contact_and_disconnect (EmpathyContactListStore *store, EmpathyContact *contact)
830 g_signal_handlers_disconnect_by_func (contact,
831 G_CALLBACK (contact_list_store_contact_updated_cb),
834 contact_list_store_remove_contact (store, contact);
838 contact_list_store_members_changed_cb (EmpathyContactList *list_iface,
839 EmpathyContact *contact,
840 EmpathyContact *actor,
844 EmpathyContactListStore *store)
846 EmpathyContactListStorePriv *priv;
848 priv = GET_PRIV (store);
850 DEBUG ("Contact %s (%d) %s",
851 empathy_contact_get_id (contact),
852 empathy_contact_get_handle (contact),
853 is_member ? "added" : "removed");
856 contact_list_store_add_contact_and_connect (store, contact);
858 contact_list_store_remove_contact_and_disconnect (store, contact);
863 contact_list_store_member_renamed_cb (EmpathyContactList *list_iface,
864 EmpathyContact *old_contact,
865 EmpathyContact *new_contact,
868 EmpathyContactListStore *store)
870 EmpathyContactListStorePriv *priv;
872 priv = GET_PRIV (store);
874 DEBUG ("Contact %s (%d) renamed to %s (%d)",
875 empathy_contact_get_id (old_contact),
876 empathy_contact_get_handle (old_contact),
877 empathy_contact_get_id (new_contact),
878 empathy_contact_get_handle (new_contact));
880 /* add the new contact */
881 contact_list_store_add_contact_and_connect (store, new_contact);
883 /* remove old contact */
884 contact_list_store_remove_contact_and_disconnect (store, old_contact);
888 contact_list_store_groups_changed_cb (EmpathyContactList *list_iface,
889 EmpathyContact *contact,
892 EmpathyContactListStore *store)
894 EmpathyContactListStorePriv *priv;
895 gboolean show_active;
897 priv = GET_PRIV (store);
899 DEBUG ("Updating groups for contact %s (%d)",
900 empathy_contact_get_id (contact),
901 empathy_contact_get_handle (contact));
903 /* We do this to make sure the groups are correct, if not, we
904 * would have to check the groups already set up for each
905 * contact and then see what has been updated.
907 show_active = priv->show_active;
908 priv->show_active = FALSE;
909 contact_list_store_remove_contact (store, contact);
910 contact_list_store_add_contact (store, contact);
911 priv->show_active = show_active;
915 contact_list_store_add_contact (EmpathyContactListStore *store,
916 EmpathyContact *contact)
918 EmpathyContactListStorePriv *priv;
920 GList *groups = NULL, *l;
921 TpConnection *connection;
922 EmpathyContactListFlags flags = 0;
924 priv = GET_PRIV (store);
926 if (EMP_STR_EMPTY (empathy_contact_get_name (contact)) ||
927 (!priv->show_offline && !empathy_contact_is_online (contact))) {
931 if (priv->show_groups) {
932 groups = empathy_contact_list_get_groups (priv->list, contact);
935 connection = empathy_contact_get_connection (contact);
936 if (EMPATHY_IS_CONTACT_MANAGER (priv->list)) {
937 flags = empathy_contact_manager_get_flags_for_connection (
938 EMPATHY_CONTACT_MANAGER (priv->list), connection);
940 /* If no groups just add it at the top level. */
942 GtkTreeModel *model = GTK_TREE_MODEL (store);
944 if (gtk_tree_model_get_iter_first (model, &iter)) do {
947 gtk_tree_model_get (model, &iter,
948 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &c,
957 } while (gtk_tree_model_iter_next (model, &iter));
959 gtk_tree_store_append (GTK_TREE_STORE (store), &iter, NULL);
960 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
961 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
962 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
963 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
964 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
965 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
966 empathy_contact_get_capabilities (contact) &
967 EMPATHY_CAPABILITIES_AUDIO,
968 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
969 empathy_contact_get_capabilities (contact) &
970 EMPATHY_CAPABILITIES_VIDEO,
971 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
975 /* Else add to each group. */
976 for (l = groups; l; l = l->next) {
977 GtkTreeIter iter_group;
979 contact_list_store_get_group (store, l->data, &iter_group, NULL, NULL);
981 gtk_tree_store_insert_after (GTK_TREE_STORE (store), &iter,
983 gtk_tree_store_set (GTK_TREE_STORE (store), &iter,
984 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
985 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, contact,
986 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
987 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
988 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
989 empathy_contact_get_capabilities (contact) &
990 EMPATHY_CAPABILITIES_AUDIO,
991 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
992 empathy_contact_get_capabilities (contact) &
993 EMPATHY_CAPABILITIES_VIDEO,
994 EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, flags,
998 g_list_free (groups);
1000 contact_list_store_contact_update (store, contact);
1005 contact_list_store_remove_contact (EmpathyContactListStore *store,
1006 EmpathyContact *contact)
1008 EmpathyContactListStorePriv *priv;
1009 GtkTreeModel *model;
1012 priv = GET_PRIV (store);
1014 iters = contact_list_store_find_contact (store, contact);
1019 /* Clean up model */
1020 model = GTK_TREE_MODEL (store);
1022 for (l = iters; l; l = l->next) {
1025 /* NOTE: it is only <= 2 here because we have
1026 * separators after the group name, otherwise it
1029 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1030 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1031 gtk_tree_store_remove (GTK_TREE_STORE (store), &parent);
1033 gtk_tree_store_remove (GTK_TREE_STORE (store), l->data);
1037 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1038 g_list_free (iters);
1042 contact_list_store_contact_update (EmpathyContactListStore *store,
1043 EmpathyContact *contact)
1045 EmpathyContactListStorePriv *priv;
1046 ShowActiveData *data;
1047 GtkTreeModel *model;
1050 gboolean should_be_in_list;
1051 gboolean was_online = TRUE;
1052 gboolean now_online = FALSE;
1053 gboolean set_model = FALSE;
1054 gboolean do_remove = FALSE;
1055 gboolean do_set_active = FALSE;
1056 gboolean do_set_refresh = FALSE;
1057 gboolean show_avatar = FALSE;
1058 GdkPixbuf *pixbuf_avatar;
1060 priv = GET_PRIV (store);
1062 model = GTK_TREE_MODEL (store);
1064 iters = contact_list_store_find_contact (store, contact);
1071 /* Get online state now. */
1072 now_online = empathy_contact_is_online (contact);
1074 if (priv->show_offline || now_online) {
1075 should_be_in_list = TRUE;
1077 should_be_in_list = FALSE;
1080 if (!in_list && !should_be_in_list) {
1081 /* Nothing to do. */
1082 DEBUG ("Contact:'%s' in list:NO, should be:NO",
1083 empathy_contact_get_name (contact));
1085 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1086 g_list_free (iters);
1089 else if (in_list && !should_be_in_list) {
1090 DEBUG ("Contact:'%s' in list:YES, should be:NO",
1091 empathy_contact_get_name (contact));
1093 if (priv->show_active) {
1095 do_set_active = TRUE;
1096 do_set_refresh = TRUE;
1099 DEBUG ("Remove item (after timeout)");
1101 DEBUG ("Remove item (now)!");
1102 contact_list_store_remove_contact (store, contact);
1105 else if (!in_list && should_be_in_list) {
1106 DEBUG ("Contact:'%s' in list:NO, should be:YES",
1107 empathy_contact_get_name (contact));
1109 contact_list_store_add_contact (store, contact);
1111 if (priv->show_active) {
1112 do_set_active = TRUE;
1114 DEBUG ("Set active (contact added)");
1117 DEBUG ("Contact:'%s' in list:YES, should be:YES",
1118 empathy_contact_get_name (contact));
1120 /* Get online state before. */
1121 if (iters && g_list_length (iters) > 0) {
1122 gtk_tree_model_get (model, iters->data,
1123 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, &was_online,
1127 /* Is this really an update or an online/offline. */
1128 if (priv->show_active) {
1129 if (was_online != now_online) {
1130 do_set_active = TRUE;
1131 do_set_refresh = TRUE;
1133 DEBUG ("Set active (contact updated %s)",
1134 was_online ? "online -> offline" :
1135 "offline -> online");
1137 /* Was TRUE for presence updates. */
1138 /* do_set_active = FALSE; */
1139 do_set_refresh = TRUE;
1141 DEBUG ("Set active (contact updated)");
1148 if (priv->show_avatars && !priv->is_compact) {
1151 pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1152 for (l = iters; l && set_model; l = l->next) {
1153 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1154 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, empathy_icon_name_for_contact (contact),
1155 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, pixbuf_avatar,
1156 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1157 EMPATHY_CONTACT_LIST_STORE_COL_NAME, empathy_contact_get_name (contact),
1158 EMPATHY_CONTACT_LIST_STORE_COL_STATUS, empathy_contact_get_status (contact),
1159 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,
1160 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, FALSE,
1161 EMPATHY_CONTACT_LIST_STORE_COL_IS_ONLINE, now_online,
1162 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1163 EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL,
1164 empathy_contact_get_capabilities (contact) &
1165 EMPATHY_CAPABILITIES_AUDIO,
1166 EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL,
1167 empathy_contact_get_capabilities (contact) &
1168 EMPATHY_CAPABILITIES_VIDEO,
1172 if (pixbuf_avatar) {
1173 g_object_unref (pixbuf_avatar);
1176 if (priv->show_active && do_set_active) {
1177 contact_list_store_contact_set_active (store, contact, do_set_active, do_set_refresh);
1179 if (do_set_active) {
1180 data = contact_list_store_contact_active_new (store, contact, do_remove);
1181 g_timeout_add_seconds (ACTIVE_USER_SHOW_TIME,
1182 (GSourceFunc) contact_list_store_contact_active_cb,
1187 /* FIXME: when someone goes online then offline quickly, the
1188 * first timeout sets the user to be inactive and the second
1189 * timeout removes the user from the contact list, really we
1190 * should remove the first timeout.
1192 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1193 g_list_free (iters);
1197 contact_list_store_contact_updated_cb (EmpathyContact *contact,
1199 EmpathyContactListStore *store)
1201 DEBUG ("Contact:'%s' updated, checking roster is in sync...",
1202 empathy_contact_get_name (contact));
1204 contact_list_store_contact_update (store, contact);
1208 contact_list_store_contact_set_active (EmpathyContactListStore *store,
1209 EmpathyContact *contact,
1211 gboolean set_changed)
1213 EmpathyContactListStorePriv *priv;
1214 GtkTreeModel *model;
1217 priv = GET_PRIV (store);
1218 model = GTK_TREE_MODEL (store);
1220 iters = contact_list_store_find_contact (store, contact);
1221 for (l = iters; l; l = l->next) {
1224 gtk_tree_store_set (GTK_TREE_STORE (store), l->data,
1225 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, active,
1228 DEBUG ("Set item %s", active ? "active" : "inactive");
1231 path = gtk_tree_model_get_path (model, l->data);
1232 gtk_tree_model_row_changed (model, path, l->data);
1233 gtk_tree_path_free (path);
1237 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1238 g_list_free (iters);
1242 static ShowActiveData *
1243 contact_list_store_contact_active_new (EmpathyContactListStore *store,
1244 EmpathyContact *contact,
1247 ShowActiveData *data;
1249 DEBUG ("Contact:'%s' now active, and %s be removed",
1250 empathy_contact_get_name (contact),
1251 remove_ ? "WILL" : "WILL NOT");
1253 data = g_slice_new0 (ShowActiveData);
1255 data->store = g_object_ref (store);
1256 data->contact = g_object_ref (contact);
1257 data->remove = remove_;
1263 contact_list_store_contact_active_free (ShowActiveData *data)
1265 g_object_unref (data->contact);
1266 g_object_unref (data->store);
1268 g_slice_free (ShowActiveData, data);
1272 contact_list_store_contact_active_cb (ShowActiveData *data)
1274 EmpathyContactListStorePriv *priv;
1276 priv = GET_PRIV (data->store);
1279 !priv->show_offline &&
1280 !empathy_contact_is_online (data->contact)) {
1281 DEBUG ("Contact:'%s' active timeout, removing item",
1282 empathy_contact_get_name (data->contact));
1283 contact_list_store_remove_contact (data->store, data->contact);
1286 DEBUG ("Contact:'%s' no longer active",
1287 empathy_contact_get_name (data->contact));
1289 contact_list_store_contact_set_active (data->store,
1294 contact_list_store_contact_active_free (data);
1300 contact_list_store_get_group_foreach (GtkTreeModel *model,
1308 /* Groups are only at the top level. */
1309 if (gtk_tree_path_get_depth (path) != 1) {
1313 gtk_tree_model_get (model, iter,
1314 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &str,
1315 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1318 if (is_group && !tp_strdiff (str, fg->name)) {
1329 contact_list_store_get_group (EmpathyContactListStore *store,
1331 GtkTreeIter *iter_group_to_set,
1332 GtkTreeIter *iter_separator_to_set,
1335 EmpathyContactListStorePriv *priv;
1336 GtkTreeModel *model;
1337 GtkTreeIter iter_group;
1338 GtkTreeIter iter_separator;
1341 priv = GET_PRIV (store);
1343 memset (&fg, 0, sizeof (fg));
1347 model = GTK_TREE_MODEL (store);
1348 gtk_tree_model_foreach (model,
1349 (GtkTreeModelForeachFunc) contact_list_store_get_group_foreach,
1357 gtk_tree_store_append (GTK_TREE_STORE (store), &iter_group, NULL);
1358 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_group,
1359 EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, NULL,
1360 EMPATHY_CONTACT_LIST_STORE_COL_NAME, name,
1361 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, TRUE,
1362 EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, FALSE,
1363 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, FALSE,
1366 if (iter_group_to_set) {
1367 *iter_group_to_set = iter_group;
1370 gtk_tree_store_append (GTK_TREE_STORE (store),
1373 gtk_tree_store_set (GTK_TREE_STORE (store), &iter_separator,
1374 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, TRUE,
1377 if (iter_separator_to_set) {
1378 *iter_separator_to_set = iter_separator;
1385 if (iter_group_to_set) {
1386 *iter_group_to_set = fg.iter;
1389 iter_separator = fg.iter;
1391 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1392 gboolean is_separator;
1394 gtk_tree_model_get (model, &iter_separator,
1395 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
1398 if (is_separator && iter_separator_to_set) {
1399 *iter_separator_to_set = iter_separator;
1406 contact_list_store_state_sort_func (GtkTreeModel *model,
1407 GtkTreeIter *iter_a,
1408 GtkTreeIter *iter_b,
1412 gchar *name_a, *name_b;
1413 gboolean is_separator_a, is_separator_b;
1414 EmpathyContact *contact_a, *contact_b;
1416 gtk_tree_model_get (model, iter_a,
1417 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1418 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1419 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1421 gtk_tree_model_get (model, iter_b,
1422 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1423 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1424 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1427 /* Separator or group? */
1428 if (is_separator_a || is_separator_b) {
1429 if (is_separator_a) {
1431 } else if (is_separator_b) {
1434 } else if (!contact_a && contact_b) {
1436 } else if (contact_a && !contact_b) {
1438 } else if (!contact_a && !contact_b) {
1440 ret_val = g_utf8_collate (name_a, name_b);
1447 /* If we managed to get this far, we can start looking at
1450 ret_val = -tp_connection_presence_type_cmp_availability (
1451 empathy_contact_get_presence (EMPATHY_CONTACT (contact_a)),
1452 empathy_contact_get_presence (EMPATHY_CONTACT (contact_b)));
1455 /* Fallback: compare by name */
1456 ret_val = g_utf8_collate (name_a, name_b);
1464 g_object_unref (contact_a);
1468 g_object_unref (contact_b);
1475 contact_list_store_name_sort_func (GtkTreeModel *model,
1476 GtkTreeIter *iter_a,
1477 GtkTreeIter *iter_b,
1480 gchar *name_a, *name_b;
1481 EmpathyContact *contact_a, *contact_b;
1482 gboolean is_separator_a, is_separator_b;
1485 gtk_tree_model_get (model, iter_a,
1486 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_a,
1487 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_a,
1488 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_a,
1490 gtk_tree_model_get (model, iter_b,
1491 EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name_b,
1492 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact_b,
1493 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator_b,
1496 /* If contact is NULL it means it's a group. */
1498 if (is_separator_a || is_separator_b) {
1499 if (is_separator_a) {
1501 } else if (is_separator_b) {
1504 } else if (!contact_a && contact_b) {
1506 } else if (contact_a && !contact_b) {
1509 ret_val = g_utf8_collate (name_a, name_b);
1516 g_object_unref (contact_a);
1520 g_object_unref (contact_b);
1527 contact_list_store_find_contact_foreach (GtkTreeModel *model,
1532 EmpathyContact *contact;
1534 gtk_tree_model_get (model, iter,
1535 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1538 if (contact == fc->contact) {
1540 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
1544 g_object_unref (contact);
1551 contact_list_store_find_contact (EmpathyContactListStore *store,
1552 EmpathyContact *contact)
1554 EmpathyContactListStorePriv *priv;
1555 GtkTreeModel *model;
1559 priv = GET_PRIV (store);
1561 memset (&fc, 0, sizeof (fc));
1563 fc.contact = contact;
1565 model = GTK_TREE_MODEL (store);
1566 gtk_tree_model_foreach (model,
1567 (GtkTreeModelForeachFunc) contact_list_store_find_contact_foreach,
1578 contact_list_store_update_list_mode_foreach (GtkTreeModel *model,
1581 EmpathyContactListStore *store)
1583 EmpathyContactListStorePriv *priv;
1584 gboolean show_avatar = FALSE;
1586 priv = GET_PRIV (store);
1588 if (priv->show_avatars && !priv->is_compact) {
1592 gtk_tree_store_set (GTK_TREE_STORE (store), iter,
1593 EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1594 EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, !priv->is_compact,