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 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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
21 * Authors: Mikael Hallendal <micke@imendio.com>
22 * Martyn Russell <martyn@imendio.com>
23 * Xavier Claessens <xclaesse@gmail.com>
33 #include <libempathy/gossip-debug.h>
35 #include "gossip-contact-list-store.h"
36 #include "gossip-contact-groups.h"
37 #include "gossip-ui-utils.h"
39 #define DEBUG_DOMAIN "ContactListStore"
41 /* Active users are those which have recently changed state
42 * (e.g. online, offline or from normal to a busy state).
45 /* Time user is shown as active */
46 #define ACTIVE_USER_SHOW_TIME 7000
48 /* Time after connecting which we wait before active users are enabled */
49 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
51 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_STORE, GossipContactListStorePriv))
53 struct _GossipContactListStorePriv
{
54 EmpathyContactList
*list
;
55 gboolean show_offline
;
56 gboolean show_avatars
;
59 GossipContactListStoreSort sort_criterium
;
62 GossipContactGroupsFunc get_contact_groups
;
63 gpointer get_contact_groups_data
;
73 GossipContact
*contact
;
79 GossipContactListStore
*store
;
80 GossipContact
*contact
;
84 static void gossip_contact_list_store_class_init (GossipContactListStoreClass
*klass
);
85 static void gossip_contact_list_store_init (GossipContactListStore
*list
);
86 static void contact_list_store_finalize (GObject
*object
);
87 static void contact_list_store_get_property (GObject
*object
,
91 static void contact_list_store_set_property (GObject
*object
,
95 static void contact_list_store_setup (GossipContactListStore
*store
);
96 static gboolean
contact_list_store_inibit_active_cb (GossipContactListStore
*store
);
97 static void contact_list_store_contact_added_cb (EmpathyContactList
*list_iface
,
98 GossipContact
*contact
,
99 GossipContactListStore
*store
);
100 static void contact_list_store_add_contact (GossipContactListStore
*store
,
101 GossipContact
*contact
);
102 static void contact_list_store_contact_removed_cb (EmpathyContactList
*list_iface
,
103 GossipContact
*contact
,
104 GossipContactListStore
*store
);
105 static void contact_list_store_remove_contact (GossipContactListStore
*store
,
106 GossipContact
*contact
);
107 static void contact_list_store_contact_update (GossipContactListStore
*store
,
108 GossipContact
*contact
);
109 static void contact_list_store_contact_groups_updated_cb (GossipContact
*contact
,
111 GossipContactListStore
*store
);
112 static void contact_list_store_contact_updated_cb (GossipContact
*contact
,
114 GossipContactListStore
*store
);
115 static void contact_list_store_contact_set_active (GossipContactListStore
*store
,
116 GossipContact
*contact
,
118 gboolean set_changed
);
119 static ShowActiveData
* contact_list_store_contact_active_new (GossipContactListStore
*store
,
120 GossipContact
*contact
,
122 static void contact_list_store_contact_active_free (ShowActiveData
*data
);
123 static gboolean
contact_list_store_contact_active_cb (ShowActiveData
*data
);
124 static gboolean
contact_list_store_get_group_foreach (GtkTreeModel
*model
,
128 static void contact_list_store_get_group (GossipContactListStore
*store
,
130 GtkTreeIter
*iter_group_to_set
,
131 GtkTreeIter
*iter_separator_to_set
,
133 static gint
contact_list_store_state_sort_func (GtkTreeModel
*model
,
137 static gint
contact_list_store_name_sort_func (GtkTreeModel
*model
,
141 static gboolean
contact_list_store_find_contact_foreach (GtkTreeModel
*model
,
145 static GList
* contact_list_store_find_contact (GossipContactListStore
*store
,
146 GossipContact
*contact
);
147 static gboolean
contact_list_store_update_list_mode_foreach (GtkTreeModel
*model
,
150 GossipContactListStore
*store
);
161 gossip_contact_list_store_sort_get_type (void)
163 static GType etype
= 0;
166 static const GEnumValue values
[] = {
167 { GOSSIP_CONTACT_LIST_STORE_SORT_NAME
,
168 "GOSSIP_CONTACT_LIST_STORE_SORT_NAME",
170 { GOSSIP_CONTACT_LIST_STORE_SORT_STATE
,
171 "GOSSIP_CONTACT_LIST_STORE_SORT_STATE",
176 etype
= g_enum_register_static ("GossipContactListStoreSort", values
);
182 G_DEFINE_TYPE (GossipContactListStore
, gossip_contact_list_store
, GTK_TYPE_TREE_STORE
);
185 gossip_contact_list_store_class_init (GossipContactListStoreClass
*klass
)
187 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
189 object_class
->finalize
= contact_list_store_finalize
;
190 object_class
->get_property
= contact_list_store_get_property
;
191 object_class
->set_property
= contact_list_store_set_property
;
193 g_object_class_install_property (object_class
,
195 g_param_spec_boolean ("show-offline",
197 "Whether contact list should display "
201 g_object_class_install_property (object_class
,
203 g_param_spec_boolean ("show-avatars",
205 "Whether contact list should display "
206 "avatars for contacts",
209 g_object_class_install_property (object_class
,
211 g_param_spec_boolean ("is-compact",
213 "Whether the contact list is in compact mode or not",
217 g_object_class_install_property (object_class
,
219 g_param_spec_enum ("sort-criterium",
221 "The sort criterium to use for sorting the contact list",
222 GOSSIP_TYPE_CONTACT_LIST_STORE_SORT
,
223 GOSSIP_CONTACT_LIST_STORE_SORT_NAME
,
226 g_type_class_add_private (object_class
, sizeof (GossipContactListStorePriv
));
230 gossip_contact_list_store_init (GossipContactListStore
*store
)
232 GossipContactListStorePriv
*priv
;
234 priv
= GET_PRIV (store
);
236 priv
->inhibit_active
= g_timeout_add (ACTIVE_USER_WAIT_TO_ENABLE_TIME
,
237 (GSourceFunc
) contact_list_store_inibit_active_cb
,
242 contact_list_store_finalize (GObject
*object
)
244 GossipContactListStorePriv
*priv
;
246 priv
= GET_PRIV (object
);
248 /* FIXME: disconnect all signals on the list and contacts */
251 g_object_unref (priv
->list
);
254 if (priv
->inhibit_active
) {
255 g_source_remove (priv
->inhibit_active
);
258 G_OBJECT_CLASS (gossip_contact_list_store_parent_class
)->finalize (object
);
262 contact_list_store_get_property (GObject
*object
,
267 GossipContactListStorePriv
*priv
;
269 priv
= GET_PRIV (object
);
272 case PROP_SHOW_OFFLINE
:
273 g_value_set_boolean (value
, priv
->show_offline
);
275 case PROP_SHOW_AVATARS
:
276 g_value_set_boolean (value
, priv
->show_avatars
);
278 case PROP_IS_COMPACT
:
279 g_value_set_boolean (value
, priv
->is_compact
);
281 case PROP_SORT_CRITERIUM
:
282 g_value_set_enum (value
, priv
->sort_criterium
);
285 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
291 contact_list_store_set_property (GObject
*object
,
296 GossipContactListStorePriv
*priv
;
298 priv
= GET_PRIV (object
);
301 case PROP_SHOW_OFFLINE
:
302 gossip_contact_list_store_set_show_offline (GOSSIP_CONTACT_LIST_STORE (object
),
303 g_value_get_boolean (value
));
305 case PROP_SHOW_AVATARS
:
306 gossip_contact_list_store_set_show_avatars (GOSSIP_CONTACT_LIST_STORE (object
),
307 g_value_get_boolean (value
));
309 case PROP_IS_COMPACT
:
310 gossip_contact_list_store_set_is_compact (GOSSIP_CONTACT_LIST_STORE (object
),
311 g_value_get_boolean (value
));
313 case PROP_SORT_CRITERIUM
:
314 gossip_contact_list_store_set_sort_criterium (GOSSIP_CONTACT_LIST_STORE (object
),
315 g_value_get_enum (value
));
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
323 GossipContactListStore
*
324 gossip_contact_list_store_new (EmpathyContactList
*list_iface
)
326 GossipContactListStore
*store
;
327 GossipContactListStorePriv
*priv
;
330 g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST (list_iface
), NULL
);
332 store
= g_object_new (GOSSIP_TYPE_CONTACT_LIST_STORE
, NULL
);
333 priv
= GET_PRIV (store
);
335 contact_list_store_setup (store
);
336 priv
->list
= g_object_ref (list_iface
);
338 /* Signal connection. */
339 g_signal_connect (priv
->list
,
341 G_CALLBACK (contact_list_store_contact_added_cb
),
343 g_signal_connect (priv
->list
,
345 G_CALLBACK (contact_list_store_contact_removed_cb
),
348 /* Add contacts already created. */
349 contacts
= empathy_contact_list_get_members (priv
->list
);
350 for (l
= contacts
; l
; l
= l
->next
) {
351 GossipContact
*contact
;
355 contact_list_store_contact_added_cb (priv
->list
, contact
, store
);
357 g_object_unref (contact
);
359 g_list_free (contacts
);
365 gossip_contact_list_store_get_list_iface (GossipContactListStore
*store
)
367 GossipContactListStorePriv
*priv
;
369 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
), FALSE
);
371 priv
= GET_PRIV (store
);
377 gossip_contact_list_store_get_show_offline (GossipContactListStore
*store
)
379 GossipContactListStorePriv
*priv
;
381 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
), FALSE
);
383 priv
= GET_PRIV (store
);
385 return priv
->show_offline
;
389 gossip_contact_list_store_set_show_offline (GossipContactListStore
*store
,
390 gboolean show_offline
)
392 GossipContactListStorePriv
*priv
;
394 gboolean show_active
;
396 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
398 priv
= GET_PRIV (store
);
400 priv
->show_offline
= show_offline
;
401 show_active
= priv
->show_active
;
403 /* Disable temporarily. */
404 priv
->show_active
= FALSE
;
406 contacts
= empathy_contact_list_get_members (priv
->list
);
407 for (l
= contacts
; l
; l
= l
->next
) {
408 GossipContact
*contact
;
410 contact
= GOSSIP_CONTACT (l
->data
);
412 contact_list_store_contact_update (store
, contact
);
414 g_object_unref (contact
);
416 g_list_free (contacts
);
418 /* Restore to original setting. */
419 priv
->show_active
= show_active
;
423 gossip_contact_list_store_get_show_avatars (GossipContactListStore
*store
)
425 GossipContactListStorePriv
*priv
;
427 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
), TRUE
);
429 priv
= GET_PRIV (store
);
431 return priv
->show_avatars
;
435 gossip_contact_list_store_set_show_avatars (GossipContactListStore
*store
,
436 gboolean show_avatars
)
438 GossipContactListStorePriv
*priv
;
441 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
443 priv
= GET_PRIV (store
);
445 priv
->show_avatars
= show_avatars
;
447 model
= GTK_TREE_MODEL (store
);
449 gtk_tree_model_foreach (model
,
450 (GtkTreeModelForeachFunc
)
451 contact_list_store_update_list_mode_foreach
,
456 gossip_contact_list_store_get_is_compact (GossipContactListStore
*store
)
458 GossipContactListStorePriv
*priv
;
460 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
), TRUE
);
462 priv
= GET_PRIV (store
);
464 return priv
->is_compact
;
468 gossip_contact_list_store_set_is_compact (GossipContactListStore
*store
,
471 GossipContactListStorePriv
*priv
;
474 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
476 priv
= GET_PRIV (store
);
478 priv
->is_compact
= is_compact
;
480 model
= GTK_TREE_MODEL (store
);
482 gtk_tree_model_foreach (model
,
483 (GtkTreeModelForeachFunc
)
484 contact_list_store_update_list_mode_foreach
,
488 GossipContactListStoreSort
489 gossip_contact_list_store_get_sort_criterium (GossipContactListStore
*store
)
491 GossipContactListStorePriv
*priv
;
493 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
), 0);
495 priv
= GET_PRIV (store
);
497 return priv
->sort_criterium
;
501 gossip_contact_list_store_set_sort_criterium (GossipContactListStore
*store
,
502 GossipContactListStoreSort sort_criterium
)
504 GossipContactListStorePriv
*priv
;
506 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
508 priv
= GET_PRIV (store
);
510 priv
->sort_criterium
= sort_criterium
;
512 switch (sort_criterium
) {
513 case GOSSIP_CONTACT_LIST_STORE_SORT_STATE
:
514 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store
),
519 case GOSSIP_CONTACT_LIST_STORE_SORT_NAME
:
520 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store
),
528 gossip_contact_list_store_row_separator_func (GtkTreeModel
*model
,
532 gboolean is_separator
= FALSE
;
534 g_return_val_if_fail (GTK_IS_TREE_MODEL (model
), FALSE
);
536 gtk_tree_model_get (model
, iter
,
537 COL_IS_SEPARATOR
, &is_separator
,
544 gossip_contact_list_store_get_parent_group (GtkTreeModel
*model
,
546 gboolean
*path_is_group
)
548 GtkTreeIter parent_iter
, iter
;
552 g_return_val_if_fail (GTK_IS_TREE_MODEL (model
), NULL
);
555 *path_is_group
= FALSE
;
558 if (!gtk_tree_model_get_iter (model
, &iter
, path
)) {
562 gtk_tree_model_get (model
, &iter
,
563 COL_IS_GROUP
, &is_group
,
571 if (!gtk_tree_model_iter_parent (model
, &parent_iter
, &iter
)) {
577 gtk_tree_model_get (model
, &iter
,
578 COL_IS_GROUP
, &is_group
,
588 *path_is_group
= TRUE
;
595 gossip_contact_list_store_search_equal_func (GtkTreeModel
*model
,
599 gpointer search_data
)
601 gchar
*name
, *name_folded
;
605 g_return_val_if_fail (GTK_IS_TREE_MODEL (model
), FALSE
);
611 gtk_tree_model_get (model
, iter
, COL_NAME
, &name
, -1);
617 name_folded
= g_utf8_casefold (name
, -1);
618 key_folded
= g_utf8_casefold (key
, -1);
620 if (name_folded
&& key_folded
&&
621 strstr (name_folded
, key_folded
)) {
628 g_free (name_folded
);
635 gossip_contact_list_store_set_contact_groups_func (GossipContactListStore
*store
,
636 GossipContactGroupsFunc func
,
639 GossipContactListStorePriv
*priv
;
642 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
644 priv
= GET_PRIV (store
);
647 priv
->get_contact_groups
= func
;
648 priv
->get_contact_groups_data
= user_data
;
650 priv
->get_contact_groups
= NULL
;
651 priv
->get_contact_groups_data
= NULL
;
654 /* If we set a custom function to get contacts groups we have to
655 * disconnect our default notify::groups signal and wait for the user
656 * to call himself gossip_contact_list_store_update_contact_groups ()
657 * when needed. If func is NULL we come back to default.
659 contacts
= empathy_contact_list_get_members (priv
->list
);
660 for (l
= contacts
; l
; l
= l
->next
) {
661 GossipContact
*contact
;
666 g_signal_handlers_disconnect_by_func (contact
,
667 G_CALLBACK (contact_list_store_contact_groups_updated_cb
),
670 g_signal_connect (contact
, "notify::groups",
671 G_CALLBACK (contact_list_store_contact_groups_updated_cb
),
675 gossip_contact_list_store_update_contact_groups (store
, contact
);
677 g_object_unref (contact
);
679 g_list_free (contacts
);
683 gossip_contact_list_store_update_contact_groups (GossipContactListStore
*store
,
684 GossipContact
*contact
)
686 GossipContactListStorePriv
*priv
;
687 gboolean show_active
;
689 g_return_if_fail (GOSSIP_IS_CONTACT_LIST_STORE (store
));
690 g_return_if_fail (GOSSIP_IS_CONTACT (contact
));
692 priv
= GET_PRIV (store
);
694 gossip_debug (DEBUG_DOMAIN
, "Contact:'%s' updating groups",
695 gossip_contact_get_name (contact
));
697 /* We do this to make sure the groups are correct, if not, we
698 * would have to check the groups already set up for each
699 * contact and then see what has been updated.
701 show_active
= priv
->show_active
;
702 priv
->show_active
= FALSE
;
703 contact_list_store_remove_contact (store
, contact
);
704 contact_list_store_add_contact (store
, contact
);
705 priv
->show_active
= show_active
;
709 contact_list_store_setup (GossipContactListStore
*store
)
711 GossipContactListStorePriv
*priv
;
712 GType types
[] = {G_TYPE_STRING
, /* Status icon-name */
713 GDK_TYPE_PIXBUF
, /* Avatar pixbuf */
714 G_TYPE_BOOLEAN
, /* Avatar pixbuf visible */
715 G_TYPE_STRING
, /* Name */
716 G_TYPE_STRING
, /* Status string */
717 G_TYPE_BOOLEAN
, /* Show status */
718 GOSSIP_TYPE_CONTACT
, /* Contact type */
719 G_TYPE_BOOLEAN
, /* Is group */
720 G_TYPE_BOOLEAN
, /* Is active */
721 G_TYPE_BOOLEAN
, /* Is online */
722 G_TYPE_BOOLEAN
}; /* Is separator */
724 priv
= GET_PRIV (store
);
726 gtk_tree_store_set_column_types (GTK_TREE_STORE (store
), COL_COUNT
, types
);
729 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store
),
731 contact_list_store_name_sort_func
,
733 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store
),
735 contact_list_store_state_sort_func
,
738 priv
->sort_criterium
= GOSSIP_CONTACT_LIST_STORE_SORT_NAME
;
739 gossip_contact_list_store_set_sort_criterium (store
, priv
->sort_criterium
);
743 contact_list_store_inibit_active_cb (GossipContactListStore
*store
)
745 GossipContactListStorePriv
*priv
;
747 priv
= GET_PRIV (store
);
749 priv
->show_active
= TRUE
;
750 priv
->inhibit_active
= 0;
756 contact_list_store_contact_added_cb (EmpathyContactList
*list_iface
,
757 GossipContact
*contact
,
758 GossipContactListStore
*store
)
760 GossipContactListStorePriv
*priv
;
762 priv
= GET_PRIV (store
);
764 gossip_debug (DEBUG_DOMAIN
,
765 "Contact:'%s' added",
766 gossip_contact_get_name (contact
));
768 if (!priv
->get_contact_groups
) {
769 g_signal_connect (contact
, "notify::groups",
770 G_CALLBACK (contact_list_store_contact_groups_updated_cb
),
773 g_signal_connect (contact
, "notify::presence",
774 G_CALLBACK (contact_list_store_contact_updated_cb
),
776 g_signal_connect (contact
, "notify::name",
777 G_CALLBACK (contact_list_store_contact_updated_cb
),
779 g_signal_connect (contact
, "notify::avatar",
780 G_CALLBACK (contact_list_store_contact_updated_cb
),
782 g_signal_connect (contact
, "notify::type",
783 G_CALLBACK (contact_list_store_contact_updated_cb
),
786 contact_list_store_add_contact (store
, contact
);
790 contact_list_store_add_contact (GossipContactListStore
*store
,
791 GossipContact
*contact
)
793 GossipContactListStorePriv
*priv
;
797 priv
= GET_PRIV (store
);
799 if (!priv
->show_offline
&& !gossip_contact_is_online (contact
)) {
803 /* If no groups just add it at the top level. */
804 if (priv
->get_contact_groups
) {
805 groups
= priv
->get_contact_groups (contact
,
806 priv
->get_contact_groups_data
);
808 groups
= gossip_contact_get_groups (contact
);
812 gtk_tree_store_append (GTK_TREE_STORE (store
), &iter
, NULL
);
813 gtk_tree_store_set (GTK_TREE_STORE (store
), &iter
,
814 COL_NAME
, gossip_contact_get_name (contact
),
815 COL_CONTACT
, contact
,
817 COL_IS_SEPARATOR
, FALSE
,
821 /* Else add to each group. */
822 for (l
= groups
; l
; l
= l
->next
) {
823 GtkTreeIter iter_group
;
831 contact_list_store_get_group (store
, name
, &iter_group
, NULL
, NULL
);
833 gtk_tree_store_insert_after (GTK_TREE_STORE (store
), &iter
,
835 gtk_tree_store_set (GTK_TREE_STORE (store
), &iter
,
836 COL_NAME
, gossip_contact_get_name (contact
),
837 COL_CONTACT
, contact
,
839 COL_IS_SEPARATOR
, FALSE
,
843 contact_list_store_contact_update (store
, contact
);
847 contact_list_store_contact_removed_cb (EmpathyContactList
*list_iface
,
848 GossipContact
*contact
,
849 GossipContactListStore
*store
)
851 gossip_debug (DEBUG_DOMAIN
, "Contact:'%s' removed",
852 gossip_contact_get_name (contact
));
854 /* Disconnect signals */
855 g_signal_handlers_disconnect_by_func (contact
,
856 G_CALLBACK (contact_list_store_contact_groups_updated_cb
),
858 g_signal_handlers_disconnect_by_func (contact
,
859 G_CALLBACK (contact_list_store_contact_updated_cb
),
862 contact_list_store_remove_contact (store
, contact
);
866 contact_list_store_remove_contact (GossipContactListStore
*store
,
867 GossipContact
*contact
)
869 GossipContactListStorePriv
*priv
;
873 priv
= GET_PRIV (store
);
875 iters
= contact_list_store_find_contact (store
, contact
);
881 model
= GTK_TREE_MODEL (store
);
883 for (l
= iters
; l
; l
= l
->next
) {
886 /* NOTE: it is only <= 2 here because we have
887 * separators after the group name, otherwise it
890 if (gtk_tree_model_iter_parent (model
, &parent
, l
->data
) &&
891 gtk_tree_model_iter_n_children (model
, &parent
) <= 2) {
892 gtk_tree_store_remove (GTK_TREE_STORE (store
), &parent
);
894 gtk_tree_store_remove (GTK_TREE_STORE (store
), l
->data
);
898 g_list_foreach (iters
, (GFunc
) gtk_tree_iter_free
, NULL
);
903 contact_list_store_contact_update (GossipContactListStore
*store
,
904 GossipContact
*contact
)
906 GossipContactListStorePriv
*priv
;
907 ShowActiveData
*data
;
911 gboolean should_be_in_list
;
912 gboolean was_online
= TRUE
;
913 gboolean now_online
= FALSE
;
914 gboolean set_model
= FALSE
;
915 gboolean do_remove
= FALSE
;
916 gboolean do_set_active
= FALSE
;
917 gboolean do_set_refresh
= FALSE
;
918 GdkPixbuf
*pixbuf_avatar
;
920 priv
= GET_PRIV (store
);
922 model
= GTK_TREE_MODEL (store
);
924 iters
= contact_list_store_find_contact (store
, contact
);
931 /* Get online state now. */
932 now_online
= gossip_contact_is_online (contact
);
934 if (priv
->show_offline
|| now_online
) {
935 should_be_in_list
= TRUE
;
937 should_be_in_list
= FALSE
;
940 if (!in_list
&& !should_be_in_list
) {
942 gossip_debug (DEBUG_DOMAIN
,
943 "Contact:'%s' in list:NO, should be:NO",
944 gossip_contact_get_name (contact
));
946 g_list_foreach (iters
, (GFunc
) gtk_tree_iter_free
, NULL
);
950 else if (in_list
&& !should_be_in_list
) {
951 gossip_debug (DEBUG_DOMAIN
,
952 "Contact:'%s' in list:YES, should be:NO",
953 gossip_contact_get_name (contact
));
955 if (priv
->show_active
) {
957 do_set_active
= TRUE
;
958 do_set_refresh
= TRUE
;
961 gossip_debug (DEBUG_DOMAIN
, "Remove item (after timeout)");
963 gossip_debug (DEBUG_DOMAIN
, "Remove item (now)!");
964 contact_list_store_remove_contact (store
, contact
);
967 else if (!in_list
&& should_be_in_list
) {
968 gossip_debug (DEBUG_DOMAIN
,
969 "Contact:'%s' in list:NO, should be:YES",
970 gossip_contact_get_name (contact
));
972 contact_list_store_add_contact (store
, contact
);
974 if (priv
->show_active
) {
975 do_set_active
= TRUE
;
977 gossip_debug (DEBUG_DOMAIN
, "Set active (contact added)");
980 gossip_debug (DEBUG_DOMAIN
,
981 "Contact:'%s' in list:YES, should be:YES",
982 gossip_contact_get_name (contact
));
984 /* Get online state before. */
985 if (iters
&& g_list_length (iters
) > 0) {
986 gtk_tree_model_get (model
, iters
->data
,
987 COL_IS_ONLINE
, &was_online
,
991 /* Is this really an update or an online/offline. */
992 if (priv
->show_active
) {
993 if (was_online
!= now_online
) {
994 do_set_active
= TRUE
;
995 do_set_refresh
= TRUE
;
997 gossip_debug (DEBUG_DOMAIN
, "Set active (contact updated %s)",
998 was_online
? "online -> offline" :
999 "offline -> online");
1001 /* Was TRUE for presence updates. */
1002 /* do_set_active = FALSE; */
1003 do_set_refresh
= TRUE
;
1005 gossip_debug (DEBUG_DOMAIN
, "Set active (contact updated)");
1012 pixbuf_avatar
= gossip_pixbuf_avatar_from_contact_scaled (contact
, 32, 32);
1013 for (l
= iters
; l
&& set_model
; l
= l
->next
) {
1014 gtk_tree_store_set (GTK_TREE_STORE (store
), l
->data
,
1015 COL_ICON_STATUS
, gossip_icon_name_for_contact (contact
),
1016 COL_PIXBUF_AVATAR
, pixbuf_avatar
,
1017 COL_PIXBUF_AVATAR_VISIBLE
, priv
->show_avatars
,
1018 COL_NAME
, gossip_contact_get_name (contact
),
1019 COL_STATUS
, gossip_contact_get_status (contact
),
1020 COL_STATUS_VISIBLE
, !priv
->is_compact
,
1021 COL_IS_GROUP
, FALSE
,
1022 COL_IS_ONLINE
, now_online
,
1023 COL_IS_SEPARATOR
, FALSE
,
1027 if (pixbuf_avatar
) {
1028 g_object_unref (pixbuf_avatar
);
1031 if (priv
->show_active
&& do_set_active
) {
1032 contact_list_store_contact_set_active (store
, contact
, do_set_active
, do_set_refresh
);
1034 if (do_set_active
) {
1035 data
= contact_list_store_contact_active_new (store
, contact
, do_remove
);
1036 g_timeout_add (ACTIVE_USER_SHOW_TIME
,
1037 (GSourceFunc
) contact_list_store_contact_active_cb
,
1042 /* FIXME: when someone goes online then offline quickly, the
1043 * first timeout sets the user to be inactive and the second
1044 * timeout removes the user from the contact list, really we
1045 * should remove the first timeout.
1047 g_list_foreach (iters
, (GFunc
) gtk_tree_iter_free
, NULL
);
1048 g_list_free (iters
);
1052 contact_list_store_contact_groups_updated_cb (GossipContact
*contact
,
1054 GossipContactListStore
*store
)
1056 gossip_contact_list_store_update_contact_groups (store
, contact
);
1060 contact_list_store_contact_updated_cb (GossipContact
*contact
,
1062 GossipContactListStore
*store
)
1064 gossip_debug (DEBUG_DOMAIN
,
1065 "Contact:'%s' updated, checking roster is in sync...",
1066 gossip_contact_get_name (contact
));
1068 contact_list_store_contact_update (store
, contact
);
1072 contact_list_store_contact_set_active (GossipContactListStore
*store
,
1073 GossipContact
*contact
,
1075 gboolean set_changed
)
1077 GossipContactListStorePriv
*priv
;
1078 GtkTreeModel
*model
;
1081 priv
= GET_PRIV (store
);
1082 model
= GTK_TREE_MODEL (store
);
1084 iters
= contact_list_store_find_contact (store
, contact
);
1085 for (l
= iters
; l
; l
= l
->next
) {
1088 gtk_tree_store_set (GTK_TREE_STORE (store
), l
->data
,
1089 COL_IS_ACTIVE
, active
,
1092 gossip_debug (DEBUG_DOMAIN
, "Set item %s", active
? "active" : "inactive");
1095 path
= gtk_tree_model_get_path (model
, l
->data
);
1096 gtk_tree_model_row_changed (model
, path
, l
->data
);
1097 gtk_tree_path_free (path
);
1101 g_list_foreach (iters
, (GFunc
) gtk_tree_iter_free
, NULL
);
1102 g_list_free (iters
);
1106 static ShowActiveData
*
1107 contact_list_store_contact_active_new (GossipContactListStore
*store
,
1108 GossipContact
*contact
,
1111 ShowActiveData
*data
;
1113 gossip_debug (DEBUG_DOMAIN
,
1114 "Contact:'%s' now active, and %s be removed",
1115 gossip_contact_get_name (contact
),
1116 remove
? "WILL" : "WILL NOT");
1118 data
= g_slice_new0 (ShowActiveData
);
1120 data
->store
= g_object_ref (store
);
1121 data
->contact
= g_object_ref (contact
);
1122 data
->remove
= remove
;
1128 contact_list_store_contact_active_free (ShowActiveData
*data
)
1130 g_object_unref (data
->contact
);
1131 g_object_unref (data
->store
);
1133 g_slice_free (ShowActiveData
, data
);
1137 contact_list_store_contact_active_cb (ShowActiveData
*data
)
1139 GossipContactListStorePriv
*priv
;
1141 priv
= GET_PRIV (data
->store
);
1144 !priv
->show_offline
&&
1145 !gossip_contact_is_online (data
->contact
)) {
1146 gossip_debug (DEBUG_DOMAIN
,
1147 "Contact:'%s' active timeout, removing item",
1148 gossip_contact_get_name (data
->contact
));
1149 contact_list_store_remove_contact (data
->store
, data
->contact
);
1152 gossip_debug (DEBUG_DOMAIN
,
1153 "Contact:'%s' no longer active",
1154 gossip_contact_get_name (data
->contact
));
1156 contact_list_store_contact_set_active (data
->store
,
1161 contact_list_store_contact_active_free (data
);
1167 contact_list_store_get_group_foreach (GtkTreeModel
*model
,
1175 /* Groups are only at the top level. */
1176 if (gtk_tree_path_get_depth (path
) != 1) {
1180 gtk_tree_model_get (model
, iter
,
1182 COL_IS_GROUP
, &is_group
,
1185 if (is_group
&& strcmp (str
, fg
->name
) == 0) {
1196 contact_list_store_get_group (GossipContactListStore
*store
,
1198 GtkTreeIter
*iter_group_to_set
,
1199 GtkTreeIter
*iter_separator_to_set
,
1202 GossipContactListStorePriv
*priv
;
1203 GtkTreeModel
*model
;
1204 GtkTreeIter iter_group
;
1205 GtkTreeIter iter_separator
;
1208 priv
= GET_PRIV (store
);
1210 memset (&fg
, 0, sizeof (fg
));
1214 model
= GTK_TREE_MODEL (store
);
1215 gtk_tree_model_foreach (model
,
1216 (GtkTreeModelForeachFunc
) contact_list_store_get_group_foreach
,
1224 gtk_tree_store_append (GTK_TREE_STORE (store
), &iter_group
, NULL
);
1225 gtk_tree_store_set (GTK_TREE_STORE (store
), &iter_group
,
1226 COL_ICON_STATUS
, NULL
,
1229 COL_IS_ACTIVE
, FALSE
,
1230 COL_IS_SEPARATOR
, FALSE
,
1233 if (iter_group_to_set
) {
1234 *iter_group_to_set
= iter_group
;
1237 gtk_tree_store_append (GTK_TREE_STORE (store
),
1240 gtk_tree_store_set (GTK_TREE_STORE (store
), &iter_separator
,
1241 COL_IS_SEPARATOR
, TRUE
,
1244 if (iter_separator_to_set
) {
1245 *iter_separator_to_set
= iter_separator
;
1252 if (iter_group_to_set
) {
1253 *iter_group_to_set
= fg
.iter
;
1256 iter_separator
= fg
.iter
;
1258 if (gtk_tree_model_iter_next (model
, &iter_separator
)) {
1259 gboolean is_separator
;
1261 gtk_tree_model_get (model
, &iter_separator
,
1262 COL_IS_SEPARATOR
, &is_separator
,
1265 if (is_separator
&& iter_separator_to_set
) {
1266 *iter_separator_to_set
= iter_separator
;
1273 contact_list_store_state_sort_func (GtkTreeModel
*model
,
1274 GtkTreeIter
*iter_a
,
1275 GtkTreeIter
*iter_b
,
1279 gchar
*name_a
, *name_b
;
1280 gboolean is_separator_a
, is_separator_b
;
1281 GossipContact
*contact_a
, *contact_b
;
1282 GossipPresence
*presence_a
, *presence_b
;
1283 McPresence state_a
, state_b
;
1285 gtk_tree_model_get (model
, iter_a
,
1287 COL_CONTACT
, &contact_a
,
1288 COL_IS_SEPARATOR
, &is_separator_a
,
1290 gtk_tree_model_get (model
, iter_b
,
1292 COL_CONTACT
, &contact_b
,
1293 COL_IS_SEPARATOR
, &is_separator_b
,
1296 /* Separator or group? */
1297 if (is_separator_a
|| is_separator_b
) {
1298 if (is_separator_a
) {
1300 } else if (is_separator_b
) {
1303 } else if (!contact_a
&& contact_b
) {
1305 } else if (contact_a
&& !contact_b
) {
1307 } else if (!contact_a
&& !contact_b
) {
1309 ret_val
= g_utf8_collate (name_a
, name_b
);
1316 /* If we managed to get this far, we can start looking at
1319 presence_a
= gossip_contact_get_presence (GOSSIP_CONTACT (contact_a
));
1320 presence_b
= gossip_contact_get_presence (GOSSIP_CONTACT (contact_b
));
1322 if (!presence_a
&& presence_b
) {
1324 } else if (presence_a
&& !presence_b
) {
1326 } else if (!presence_a
&& !presence_b
) {
1327 /* Both offline, sort by name */
1328 ret_val
= g_utf8_collate (name_a
, name_b
);
1330 state_a
= gossip_presence_get_state (presence_a
);
1331 state_b
= gossip_presence_get_state (presence_b
);
1333 if (state_a
< state_b
) {
1335 } else if (state_a
> state_b
) {
1338 /* Fallback: compare by name */
1339 ret_val
= g_utf8_collate (name_a
, name_b
);
1348 g_object_unref (contact_a
);
1352 g_object_unref (contact_b
);
1359 contact_list_store_name_sort_func (GtkTreeModel
*model
,
1360 GtkTreeIter
*iter_a
,
1361 GtkTreeIter
*iter_b
,
1364 gchar
*name_a
, *name_b
;
1365 GossipContact
*contact_a
, *contact_b
;
1366 gboolean is_separator_a
, is_separator_b
;
1369 gtk_tree_model_get (model
, iter_a
,
1371 COL_CONTACT
, &contact_a
,
1372 COL_IS_SEPARATOR
, &is_separator_a
,
1374 gtk_tree_model_get (model
, iter_b
,
1376 COL_CONTACT
, &contact_b
,
1377 COL_IS_SEPARATOR
, &is_separator_b
,
1380 /* If contact is NULL it means it's a group. */
1382 if (is_separator_a
|| is_separator_b
) {
1383 if (is_separator_a
) {
1385 } else if (is_separator_b
) {
1388 } else if (!contact_a
&& contact_b
) {
1390 } else if (contact_a
&& !contact_b
) {
1393 ret_val
= g_utf8_collate (name_a
, name_b
);
1400 g_object_unref (contact_a
);
1404 g_object_unref (contact_b
);
1411 contact_list_store_find_contact_foreach (GtkTreeModel
*model
,
1416 GossipContact
*contact
;
1418 gtk_tree_model_get (model
, iter
,
1419 COL_CONTACT
, &contact
,
1426 if (gossip_contact_equal (contact
, fc
->contact
)) {
1428 fc
->iters
= g_list_append (fc
->iters
, gtk_tree_iter_copy (iter
));
1430 g_object_unref (contact
);
1436 contact_list_store_find_contact (GossipContactListStore
*store
,
1437 GossipContact
*contact
)
1439 GossipContactListStorePriv
*priv
;
1440 GtkTreeModel
*model
;
1444 priv
= GET_PRIV (store
);
1446 memset (&fc
, 0, sizeof (fc
));
1448 fc
.contact
= contact
;
1450 model
= GTK_TREE_MODEL (store
);
1451 gtk_tree_model_foreach (model
,
1452 (GtkTreeModelForeachFunc
) contact_list_store_find_contact_foreach
,
1463 contact_list_store_update_list_mode_foreach (GtkTreeModel
*model
,
1466 GossipContactListStore
*store
)
1468 GossipContactListStorePriv
*priv
;
1469 gboolean show_avatar
= FALSE
;
1471 priv
= GET_PRIV (store
);
1473 if (priv
->show_avatars
&& !priv
->is_compact
) {
1477 gtk_tree_store_set (GTK_TREE_STORE (store
), iter
,
1478 COL_PIXBUF_AVATAR_VISIBLE
, show_avatar
,
1479 COL_STATUS_VISIBLE
, !priv
->is_compact
,