3 #include "empathy-roster-view.h"
5 #include <glib/gi18n-lib.h>
7 #include "libempathy/empathy-contact-groups.h"
9 #include "libempathy-gtk/empathy-roster-contact.h"
10 #include "libempathy-gtk/empathy-roster-group.h"
11 #include "libempathy-gtk/empathy-ui-utils.h"
13 G_DEFINE_TYPE (EmpathyRosterView, empathy_roster_view, EGG_TYPE_LIST_BOX)
15 /* Flashing delay for icons (milliseconds). */
16 #define FLASH_TIMEOUT 500
18 /* Delay in milliseconds between the last stroke on the keyboard and the start
19 * of the live search. */
20 #define SEARCH_TIMEOUT 500
33 SIG_INDIVIDUAL_ACTIVATED,
34 SIG_POPUP_INDIVIDUAL_MENU,
36 SIG_INDIVIDUAL_TOOLTIP,
40 static guint signals[LAST_SIGNAL];
42 #define NO_GROUP "X-no-group"
44 struct _EmpathyRosterViewPriv
46 /* FolksIndividual (borrowed) -> GHashTable (
47 * (gchar * group_name) -> EmpathyRosterContact (borrowed))
49 * When not using groups, this hash just have one element mapped
50 * from the special NO_GROUP key. We could use it as a set but
51 * I prefer to stay coherent in the way this hash is managed.
53 GHashTable *roster_contacts;
54 /* (gchar *group_name) -> EmpathyRosterGroup (borrowed) */
55 GHashTable *roster_groups;
56 /* Hash of the EmpathyRosterContact currently displayed */
57 GHashTable *displayed_contacts;
60 /* queue of (Event *). The most recent events are in the head of the queue
61 * so we always display the icon of the oldest one. */
64 gboolean display_flash_event;
68 gboolean show_offline;
72 EmpathyLiveSearch *search;
74 EmpathyRosterModel *model;
80 FolksIndividual *individual;
87 FolksIndividual *individual,
91 Event *event = g_slice_new (Event);
94 event->individual = g_object_ref (individual);
95 event->icon = g_strdup (icon);
96 event->user_data = user_data;
101 event_free (gpointer data)
104 g_object_unref (event->individual);
105 g_free (event->icon);
107 g_slice_free (Event, event);
111 empathy_roster_view_get_property (GObject *object,
116 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
121 g_value_set_object (value, self->priv->model);
123 case PROP_SHOW_OFFLINE:
124 g_value_set_boolean (value, self->priv->show_offline);
126 case PROP_SHOW_GROUPS:
127 g_value_set_boolean (value, self->priv->show_groups);
130 g_value_set_boolean (value, self->priv->empty);
133 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
139 empathy_roster_view_set_property (GObject *object,
144 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
149 g_assert (self->priv->model == NULL);
150 self->priv->model = g_value_dup_object (value);
152 case PROP_SHOW_OFFLINE:
153 empathy_roster_view_show_offline (self, g_value_get_boolean (value));
155 case PROP_SHOW_GROUPS:
156 empathy_roster_view_show_groups (self, g_value_get_boolean (value));
159 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
165 roster_contact_changed_cb (GtkWidget *child,
167 EmpathyRosterView *self)
169 egg_list_box_child_changed (EGG_LIST_BOX (self), child);
173 add_roster_contact (EmpathyRosterView *self,
174 FolksIndividual *individual,
179 contact = empathy_roster_contact_new (individual, group);
181 /* Need to refilter if online is changed */
182 g_signal_connect (contact, "notify::online",
183 G_CALLBACK (roster_contact_changed_cb), self);
185 /* Need to resort if alias is changed */
186 g_signal_connect (contact, "notify::alias",
187 G_CALLBACK (roster_contact_changed_cb), self);
189 gtk_widget_show (contact);
190 gtk_container_add (GTK_CONTAINER (self), contact);
196 group_expanded_cb (EmpathyRosterGroup *group,
198 EmpathyRosterView *self)
202 widgets = empathy_roster_group_get_widgets (group);
203 for (l = widgets; l != NULL; l = g_list_next (l))
205 egg_list_box_child_changed (EGG_LIST_BOX (self), l->data);
208 g_list_free (widgets);
210 empathy_contact_group_set_expanded (empathy_roster_group_get_name (group),
211 gtk_expander_get_expanded (GTK_EXPANDER (group)));
214 static EmpathyRosterGroup *
215 lookup_roster_group (EmpathyRosterView *self,
218 return g_hash_table_lookup (self->priv->roster_groups, group);
221 static EmpathyRosterGroup *
222 ensure_roster_group (EmpathyRosterView *self,
225 GtkWidget *roster_group;
227 roster_group = (GtkWidget *) lookup_roster_group (self, group);
228 if (roster_group != NULL)
229 return EMPATHY_ROSTER_GROUP (roster_group);
231 if (!tp_strdiff (group, EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP))
232 roster_group = empathy_roster_group_new (group, "emblem-favorite-symbolic");
233 else if (!tp_strdiff (group, EMPATHY_ROSTER_MODEL_GROUP_PEOPLE_NEARBY))
234 roster_group = empathy_roster_group_new (group, "im-local-xmpp");
236 roster_group = empathy_roster_group_new (group, NULL);
238 gtk_expander_set_expanded (GTK_EXPANDER (roster_group),
239 empathy_contact_group_get_expanded (group));
241 g_signal_connect (roster_group, "notify::expanded",
242 G_CALLBACK (group_expanded_cb), self);
244 gtk_widget_show (roster_group);
245 gtk_container_add (GTK_CONTAINER (self), roster_group);
247 g_hash_table_insert (self->priv->roster_groups, g_strdup (group),
250 return EMPATHY_ROSTER_GROUP (roster_group);
254 update_empty (EmpathyRosterView *self,
257 if (self->priv->empty == empty)
260 self->priv->empty = empty;
261 g_object_notify (G_OBJECT (self), "empty");
264 static gboolean filter_group (EmpathyRosterView *self,
265 EmpathyRosterGroup *group);
268 at_least_one_group_displayed (EmpathyRosterView *self)
273 g_hash_table_iter_init (&iter, self->priv->roster_groups);
274 while (g_hash_table_iter_next (&iter, NULL, &v))
276 EmpathyRosterGroup *group = EMPATHY_ROSTER_GROUP (v);
278 if (filter_group (self, group))
286 check_if_empty (EmpathyRosterView *self)
288 /* Roster is considered as empty if there is no contact *and* no group
289 * currently displayed. */
290 if (g_hash_table_size (self->priv->displayed_contacts) != 0 ||
291 at_least_one_group_displayed (self))
293 update_empty (self, FALSE);
297 update_empty (self, TRUE);
301 update_group_widgets (EmpathyRosterView *self,
302 EmpathyRosterGroup *group,
303 EmpathyRosterContact *contact,
306 guint old_count, count;
308 old_count = empathy_roster_group_get_widgets_count (group);
311 count = empathy_roster_group_add_widget (group, GTK_WIDGET (contact));
313 count = empathy_roster_group_remove_widget (group, GTK_WIDGET (contact));
315 if (count != old_count)
317 egg_list_box_child_changed (EGG_LIST_BOX (self), GTK_WIDGET (group));
319 check_if_empty (self);
324 add_to_group (EmpathyRosterView *self,
325 FolksIndividual *individual,
329 GHashTable *contacts;
330 EmpathyRosterGroup *roster_group = NULL;
332 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
333 if (contacts == NULL)
336 if (g_hash_table_lookup (contacts, group) != NULL)
339 if (tp_strdiff (group, NO_GROUP))
340 roster_group = ensure_roster_group (self, group);
342 contact = add_roster_contact (self, individual, group);
343 g_hash_table_insert (contacts, g_strdup (group), contact);
345 if (roster_group != NULL)
347 update_group_widgets (self, roster_group,
348 EMPATHY_ROSTER_CONTACT (contact), TRUE);
353 individual_favourite_change_cb (FolksIndividual *individual,
355 EmpathyRosterView *self)
357 /* We may have to refilter the contact as only favorite contacts are always
358 * displayed regardless of their presence. */
359 GHashTable *contacts;
362 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
363 if (contacts == NULL)
366 if (self->priv->show_groups)
367 contact = g_hash_table_lookup (contacts,
368 EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP);
370 contact = g_hash_table_lookup (contacts, NO_GROUP);
375 egg_list_box_child_changed (EGG_LIST_BOX (self), contact);
379 individual_added (EmpathyRosterView *self,
380 FolksIndividual *individual)
382 GHashTable *contacts;
384 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
385 if (contacts != NULL)
388 contacts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
390 g_hash_table_insert (self->priv->roster_contacts, individual, contacts);
392 if (!self->priv->show_groups)
394 add_to_group (self, individual, NO_GROUP);
400 groups = empathy_roster_model_dup_groups_for_individual (self->priv->model,
403 if (g_list_length (groups) > 0)
405 for (l = groups; l != NULL; l = g_list_next (l))
407 add_to_group (self, individual, l->data);
412 /* No group, adds to Ungrouped */
413 add_to_group (self, individual, EMPATHY_ROSTER_MODEL_GROUP_UNGROUPED);
416 g_list_free_full (groups, g_free);
419 tp_g_signal_connect_object (individual, "notify::is-favourite",
420 G_CALLBACK (individual_favourite_change_cb), self, 0);
424 set_event_icon_on_individual (EmpathyRosterView *self,
425 FolksIndividual *individual,
428 GHashTable *contacts;
432 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
433 if (contacts == NULL)
436 g_hash_table_iter_init (&iter, contacts);
437 while (g_hash_table_iter_next (&iter, NULL, &v))
439 EmpathyRosterContact *contact =v;
441 empathy_roster_contact_set_event_icon (contact, icon);
446 flash_event (Event *event,
447 EmpathyRosterView *self)
449 set_event_icon_on_individual (self, event->individual, event->icon);
453 unflash_event (Event *event,
454 EmpathyRosterView *self)
456 set_event_icon_on_individual (self, event->individual, NULL);
460 flash_cb (gpointer data)
462 EmpathyRosterView *self = data;
464 if (self->priv->display_flash_event)
466 g_queue_foreach (self->priv->events, (GFunc) flash_event, self);
467 self->priv->display_flash_event = FALSE;
471 g_queue_foreach (self->priv->events, (GFunc) unflash_event, self);
472 self->priv->display_flash_event = TRUE;
479 start_flashing (EmpathyRosterView *self)
481 if (self->priv->flash_id != 0)
484 self->priv->display_flash_event = TRUE;
486 self->priv->flash_id = g_timeout_add (FLASH_TIMEOUT,
491 stop_flashing (EmpathyRosterView *self)
493 if (self->priv->flash_id == 0)
496 g_source_remove (self->priv->flash_id);
497 self->priv->flash_id = 0;
501 remove_event (EmpathyRosterView *self,
504 unflash_event (event, self);
505 g_queue_remove (self->priv->events, event);
507 if (g_queue_get_length (self->priv->events) == 0)
509 stop_flashing (self);
514 remove_all_individual_event (EmpathyRosterView *self,
515 FolksIndividual *individual)
519 for (l = g_queue_peek_head_link (self->priv->events); l != NULL;
522 Event *event = l->data;
524 if (event->individual == individual)
526 remove_event (self, event);
533 individual_removed (EmpathyRosterView *self,
534 FolksIndividual *individual)
536 GHashTable *contacts;
540 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
541 if (contacts == NULL)
544 remove_all_individual_event (self, individual);
546 g_hash_table_iter_init (&iter, contacts);
547 while (g_hash_table_iter_next (&iter, &key, &value))
549 const gchar *group_name = key;
550 GtkWidget *contact = value;
551 EmpathyRosterGroup *group;
553 group = lookup_roster_group (self, group_name);
556 update_group_widgets (self, group,
557 EMPATHY_ROSTER_CONTACT (contact), FALSE);
560 gtk_container_remove (GTK_CONTAINER (self), contact);
563 g_hash_table_remove (self->priv->roster_contacts, individual);
567 individual_added_cb (EmpathyRosterModel *model,
568 FolksIndividual *individual,
569 EmpathyRosterView *self)
571 individual_added (self, individual);
575 individual_removed_cb (EmpathyRosterModel *model,
576 FolksIndividual *individual,
577 EmpathyRosterView *self)
579 individual_removed (self, individual);
583 contact_in_top (EmpathyRosterView *self,
584 EmpathyRosterContact *contact)
586 if (!self->priv->show_groups)
588 /* Always display top contacts in non-group mode. */
590 FolksIndividual *individual;
591 gboolean result = FALSE;
593 individual = empathy_roster_contact_get_individual (contact);
595 groups = empathy_roster_model_dup_groups_for_individual (
596 self->priv->model, individual);
598 if (g_list_find_custom (groups, EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP,
599 (GCompareFunc) g_strcmp0) != NULL)
602 g_list_free_full (groups, g_free);
607 if (!tp_strdiff (empathy_roster_contact_get_group (contact),
608 EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP))
609 /* If we are displaying contacts, we only want to *always* display the
610 * RosterContact which is displayed at the top; not the ones displayed in
611 * the 'normal' group sections */
618 compare_roster_contacts_by_alias (EmpathyRosterContact *a,
619 EmpathyRosterContact *b)
621 FolksIndividual *ind_a, *ind_b;
622 const gchar *alias_a, *alias_b;
624 ind_a = empathy_roster_contact_get_individual (a);
625 ind_b = empathy_roster_contact_get_individual (b);
627 alias_a = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_a));
628 alias_b = folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (ind_b));
630 return g_ascii_strcasecmp (alias_a, alias_b);
634 compare_roster_contacts_no_group (EmpathyRosterView *self,
635 EmpathyRosterContact *a,
636 EmpathyRosterContact *b)
638 gboolean top_a, top_b;
640 top_a = contact_in_top (self, a);
641 top_b = contact_in_top (self, b);
644 /* Both contacts are in the top of the roster (or not). Sort them
646 return compare_roster_contacts_by_alias (a, b);
654 compare_group_names (const gchar *group_a,
655 const gchar *group_b)
657 if (!tp_strdiff (group_a, EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP))
660 if (!tp_strdiff (group_b, EMPATHY_ROSTER_MODEL_GROUP_TOP_GROUP))
663 if (!tp_strdiff (group_a, EMPATHY_ROSTER_MODEL_GROUP_UNGROUPED))
665 else if (!tp_strdiff (group_b, EMPATHY_ROSTER_MODEL_GROUP_UNGROUPED))
668 return g_ascii_strcasecmp (group_a, group_b);
672 compare_roster_contacts_with_groups (EmpathyRosterView *self,
673 EmpathyRosterContact *a,
674 EmpathyRosterContact *b)
676 const gchar *group_a, *group_b;
678 group_a = empathy_roster_contact_get_group (a);
679 group_b = empathy_roster_contact_get_group (b);
681 if (!tp_strdiff (group_a, group_b))
682 /* Same group, compare the contacts */
683 return compare_roster_contacts_by_alias (a, b);
686 return compare_group_names (group_a, group_b);
690 compare_roster_contacts (EmpathyRosterView *self,
691 EmpathyRosterContact *a,
692 EmpathyRosterContact *b)
694 if (!self->priv->show_groups)
695 return compare_roster_contacts_no_group (self, a, b);
697 return compare_roster_contacts_with_groups (self, a, b);
701 compare_roster_groups (EmpathyRosterGroup *a,
702 EmpathyRosterGroup *b)
704 const gchar *name_a, *name_b;
706 name_a = empathy_roster_group_get_name (a);
707 name_b = empathy_roster_group_get_name (b);
709 return compare_group_names (name_a, name_b);
713 compare_contact_group (EmpathyRosterContact *contact,
714 EmpathyRosterGroup *group)
716 const char *contact_group, *group_name;
718 contact_group = empathy_roster_contact_get_group (contact);
719 group_name = empathy_roster_group_get_name (group);
721 if (!tp_strdiff (contact_group, group_name))
722 /* @contact is in @group, @group has to be displayed first */
725 /* @contact is in a different group, sort by group name */
726 return compare_group_names (contact_group, group_name);
730 roster_view_sort (gconstpointer a,
734 EmpathyRosterView *self = user_data;
736 if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_CONTACT (b))
737 return compare_roster_contacts (self, EMPATHY_ROSTER_CONTACT (a),
738 EMPATHY_ROSTER_CONTACT (b));
739 else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_GROUP (b))
740 return compare_roster_groups (EMPATHY_ROSTER_GROUP (a),
741 EMPATHY_ROSTER_GROUP (b));
742 else if (EMPATHY_IS_ROSTER_CONTACT (a) && EMPATHY_IS_ROSTER_GROUP (b))
743 return compare_contact_group (EMPATHY_ROSTER_CONTACT (a),
744 EMPATHY_ROSTER_GROUP (b));
745 else if (EMPATHY_IS_ROSTER_GROUP (a) && EMPATHY_IS_ROSTER_CONTACT (b))
746 return -1 * compare_contact_group (EMPATHY_ROSTER_CONTACT (b),
747 EMPATHY_ROSTER_GROUP (a));
749 g_return_val_if_reached (0);
753 update_separator (GtkWidget **separator,
760 /* No separator before the first row */
761 g_clear_object (separator);
765 if (*separator != NULL)
768 *separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
769 g_object_ref_sink (*separator);
773 is_searching (EmpathyRosterView *self)
775 if (self->priv->search == NULL)
778 return gtk_widget_get_visible (GTK_WIDGET (self->priv->search));
782 add_to_displayed (EmpathyRosterView *self,
783 EmpathyRosterContact *contact)
785 FolksIndividual *individual;
786 GHashTable *contacts;
790 if (g_hash_table_lookup (self->priv->displayed_contacts, contact) != NULL)
793 g_hash_table_add (self->priv->displayed_contacts, contact);
794 update_empty (self, FALSE);
796 /* Groups of this contact may now be displayed if we just displays the first
797 * child in this group. */
799 if (!self->priv->show_groups)
802 individual = empathy_roster_contact_get_individual (contact);
803 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
804 if (contacts == NULL)
807 g_hash_table_iter_init (&iter, contacts);
808 while (g_hash_table_iter_next (&iter, &k, NULL))
810 const gchar *group_name = k;
813 group = g_hash_table_lookup (self->priv->roster_groups, group_name);
817 egg_list_box_child_changed (EGG_LIST_BOX (self), group);
822 remove_from_displayed (EmpathyRosterView *self,
823 EmpathyRosterContact *contact)
825 g_hash_table_remove (self->priv->displayed_contacts, contact);
827 check_if_empty (self);
831 contact_is_favorite (EmpathyRosterContact *contact)
833 FolksIndividual *individual;
835 individual = empathy_roster_contact_get_individual (contact);
837 return folks_favourite_details_get_is_favourite (
838 FOLKS_FAVOURITE_DETAILS (individual));
842 * check if @contact should be displayed according to @self's current status
843 * and without consideration for the state of @contact's groups.
846 contact_should_be_displayed (EmpathyRosterView *self,
847 EmpathyRosterContact *contact)
849 if (is_searching (self))
851 FolksIndividual *individual;
853 individual = empathy_roster_contact_get_individual (contact);
855 return empathy_individual_match_string (individual,
856 empathy_live_search_get_text (self->priv->search),
857 empathy_live_search_get_words (self->priv->search));
860 if (self->priv->show_offline)
863 if (contact_in_top (self, contact) &&
864 contact_is_favorite (contact))
865 /* Favorite top contacts are always displayed */
868 return empathy_roster_contact_is_online (contact);
873 filter_contact (EmpathyRosterView *self,
874 EmpathyRosterContact *contact)
878 displayed = contact_should_be_displayed (self, contact);
880 if (self->priv->show_groups)
882 const gchar *group_name;
883 EmpathyRosterGroup *group;
885 group_name = empathy_roster_contact_get_group (contact);
886 group = lookup_roster_group (self, group_name);
890 /* When searching, always display even if the group is closed */
891 if (!is_searching (self) &&
892 !gtk_expander_get_expanded (GTK_EXPANDER (group)))
899 add_to_displayed (self, contact);
903 remove_from_displayed (self, contact);
910 filter_group (EmpathyRosterView *self,
911 EmpathyRosterGroup *group)
914 gboolean result = FALSE;
916 /* Display the group if it contains at least one displayed contact */
917 widgets = empathy_roster_group_get_widgets (group);
918 for (l = widgets; l != NULL; l = g_list_next (l))
920 EmpathyRosterContact *contact = l->data;
922 if (contact_should_be_displayed (self, contact))
929 g_list_free (widgets);
935 filter_list (GtkWidget *child,
938 EmpathyRosterView *self = user_data;
940 if (EMPATHY_IS_ROSTER_CONTACT (child))
941 return filter_contact (self, EMPATHY_ROSTER_CONTACT (child));
943 else if (EMPATHY_IS_ROSTER_GROUP (child))
944 return filter_group (self, EMPATHY_ROSTER_GROUP (child));
946 g_return_val_if_reached (FALSE);
950 populate_view (EmpathyRosterView *self)
952 GList *individuals, *l;
954 individuals = empathy_roster_model_get_individuals (self->priv->model);
955 for (l = individuals; l != NULL; l = g_list_next (l))
957 FolksIndividual *individual = l->data;
959 individual_added (self, individual);
962 g_list_free (individuals);
966 remove_from_group (EmpathyRosterView *self,
967 FolksIndividual *individual,
970 GHashTable *contacts;
972 EmpathyRosterGroup *roster_group;
974 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
975 if (contacts == NULL)
978 contact = g_hash_table_lookup (contacts, group);
982 g_hash_table_remove (contacts, group);
984 if (g_hash_table_size (contacts) == 0)
986 add_to_group (self, individual, EMPATHY_ROSTER_MODEL_GROUP_UNGROUPED);
989 roster_group = lookup_roster_group (self, group);
991 if (roster_group != NULL)
993 update_group_widgets (self, roster_group,
994 EMPATHY_ROSTER_CONTACT (contact), FALSE);
997 gtk_container_remove (GTK_CONTAINER (self), contact);
1001 groups_changed_cb (EmpathyRosterModel *model,
1002 FolksIndividual *individual,
1005 EmpathyRosterView *self)
1007 if (!self->priv->show_groups)
1009 egg_list_box_resort (EGG_LIST_BOX (self));
1015 add_to_group (self, individual, group);
1019 remove_from_group (self, individual, group);
1024 empathy_roster_view_constructed (GObject *object)
1026 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
1027 void (*chain_up) (GObject *) =
1028 ((GObjectClass *) empathy_roster_view_parent_class)->constructed;
1030 if (chain_up != NULL)
1033 g_assert (EMPATHY_IS_ROSTER_MODEL (self->priv->model));
1035 /* Get saved group states. */
1036 empathy_contact_groups_get_all ();
1038 populate_view (self);
1040 tp_g_signal_connect_object (self->priv->model, "individual-added",
1041 G_CALLBACK (individual_added_cb), self, 0);
1042 tp_g_signal_connect_object (self->priv->model, "individual-removed",
1043 G_CALLBACK (individual_removed_cb), self, 0);
1044 tp_g_signal_connect_object (self->priv->model, "groups-changed",
1045 G_CALLBACK (groups_changed_cb), self, 0);
1047 egg_list_box_set_sort_func (EGG_LIST_BOX (self),
1048 roster_view_sort, self, NULL);
1050 egg_list_box_set_separator_funcs (EGG_LIST_BOX (self), update_separator,
1053 egg_list_box_set_filter_func (EGG_LIST_BOX (self), filter_list, self, NULL);
1055 egg_list_box_set_activate_on_single_click (EGG_LIST_BOX (self), FALSE);
1059 clear_view (EmpathyRosterView *self)
1061 g_hash_table_remove_all (self->priv->roster_contacts);
1062 g_hash_table_remove_all (self->priv->roster_groups);
1063 g_hash_table_remove_all (self->priv->displayed_contacts);
1065 gtk_container_foreach (GTK_CONTAINER (self),
1066 (GtkCallback) gtk_widget_destroy, NULL);
1070 empathy_roster_view_dispose (GObject *object)
1072 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
1073 void (*chain_up) (GObject *) =
1074 ((GObjectClass *) empathy_roster_view_parent_class)->dispose;
1076 /* Start by clearing the view so our internal hash tables are cleared from
1077 * objects being destroyed. */
1080 stop_flashing (self);
1082 empathy_roster_view_set_live_search (self, NULL);
1083 g_clear_object (&self->priv->model);
1085 if (self->priv->search_id != 0)
1087 g_source_remove (self->priv->search_id);
1088 self->priv->search_id = 0;
1091 if (chain_up != NULL)
1096 empathy_roster_view_finalize (GObject *object)
1098 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (object);
1099 void (*chain_up) (GObject *) =
1100 ((GObjectClass *) empathy_roster_view_parent_class)->finalize;
1102 g_hash_table_unref (self->priv->roster_contacts);
1103 g_hash_table_unref (self->priv->roster_groups);
1104 g_hash_table_unref (self->priv->displayed_contacts);
1105 g_queue_free_full (self->priv->events, event_free);
1107 if (chain_up != NULL)
1112 empathy_roster_view_child_activated (EggListBox *box,
1115 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (box);
1116 EmpathyRosterContact *contact;
1117 FolksIndividual *individual;
1120 if (!EMPATHY_IS_ROSTER_CONTACT (child))
1123 contact = EMPATHY_ROSTER_CONTACT (child);
1124 individual = empathy_roster_contact_get_individual (contact);
1126 /* Activate the oldest event associated with this contact, if any */
1127 for (l = g_queue_peek_tail_link (self->priv->events); l != NULL;
1128 l = g_list_previous (l))
1130 Event *event = l->data;
1132 if (event->individual == individual)
1134 g_signal_emit (box, signals[SIG_EVENT_ACTIVATED], 0, individual,
1140 g_signal_emit (box, signals[SIG_INDIVIDUAL_ACTIVATED], 0, individual);
1144 fire_popup_individual_menu (EmpathyRosterView *self,
1149 EmpathyRosterContact *contact;
1150 FolksIndividual *individual;
1152 if (!EMPATHY_IS_ROSTER_CONTACT (child))
1155 contact = EMPATHY_ROSTER_CONTACT (child);
1156 individual = empathy_roster_contact_get_individual (contact);
1158 g_signal_emit (self, signals[SIG_POPUP_INDIVIDUAL_MENU], 0,
1159 individual, button, time);
1163 empathy_roster_view_button_press_event (GtkWidget *widget,
1164 GdkEventButton *event)
1166 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (widget);
1167 gboolean (*chain_up) (GtkWidget *, GdkEventButton *) =
1168 ((GtkWidgetClass *) empathy_roster_view_parent_class)->button_press_event;
1170 if (event->button == 3)
1174 child = egg_list_box_get_child_at_y (EGG_LIST_BOX (self), event->y);
1178 egg_list_box_select_child (EGG_LIST_BOX (self), child);
1180 fire_popup_individual_menu (self, child, event->button, event->time);
1184 return chain_up (widget, event);
1188 empathy_roster_view_key_press_event (GtkWidget *widget,
1191 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (widget);
1192 gboolean (*chain_up) (GtkWidget *, GdkEventKey *) =
1193 ((GtkWidgetClass *) empathy_roster_view_parent_class)->key_press_event;
1195 if (event->keyval == GDK_KEY_Menu)
1199 child = egg_list_box_get_selected_child (EGG_LIST_BOX (self));
1202 fire_popup_individual_menu (self, child, 0, event->time);
1205 return chain_up (widget, event);
1209 * @out_child: (out) (allow-none)
1212 empathy_roster_view_get_individual_at_y (EmpathyRosterView *self,
1214 GtkWidget **out_child)
1218 child = egg_list_box_get_child_at_y (EGG_LIST_BOX (self), y);
1220 if (out_child != NULL)
1223 if (!EMPATHY_IS_ROSTER_CONTACT (child))
1226 return empathy_roster_contact_get_individual (EMPATHY_ROSTER_CONTACT (child));
1230 * @out_child: (out) (allow-none)
1233 empathy_roster_view_get_group_at_y (EmpathyRosterView *self,
1238 child = egg_list_box_get_child_at_y (EGG_LIST_BOX (self), y);
1240 if (EMPATHY_IS_ROSTER_CONTACT (child))
1241 return empathy_roster_contact_get_group (EMPATHY_ROSTER_CONTACT (child));
1242 else if (EMPATHY_IS_ROSTER_GROUP (child))
1243 return empathy_roster_group_get_name (EMPATHY_ROSTER_GROUP (child));
1249 empathy_roster_view_query_tooltip (GtkWidget *widget,
1252 gboolean keyboard_mode,
1253 GtkTooltip *tooltip)
1255 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (widget);
1256 FolksIndividual *individual;
1260 individual = empathy_roster_view_get_individual_at_y (self, y, &child);
1261 if (individual == NULL)
1264 g_signal_emit (self, signals[SIG_INDIVIDUAL_TOOLTIP], 0,
1265 individual, keyboard_mode, tooltip, &result);
1269 GtkAllocation allocation;
1271 gtk_widget_get_allocation (child, &allocation);
1272 gtk_tooltip_set_tip_area (tooltip, (GdkRectangle *) &allocation);
1279 empathy_roster_view_remove (GtkContainer *container,
1282 EmpathyRosterView *self = EMPATHY_ROSTER_VIEW (container);
1283 void (*chain_up) (GtkContainer *, GtkWidget *) =
1284 ((GtkContainerClass *) empathy_roster_view_parent_class)->remove;
1286 chain_up (container, widget);
1288 if (EMPATHY_IS_ROSTER_CONTACT (widget))
1289 remove_from_displayed (self, (EmpathyRosterContact *) widget);
1293 empathy_roster_view_class_init (
1294 EmpathyRosterViewClass *klass)
1296 GObjectClass *oclass = G_OBJECT_CLASS (klass);
1297 EggListBoxClass *box_class = EGG_LIST_BOX_CLASS (klass);
1298 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1299 GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1302 oclass->get_property = empathy_roster_view_get_property;
1303 oclass->set_property = empathy_roster_view_set_property;
1304 oclass->constructed = empathy_roster_view_constructed;
1305 oclass->dispose = empathy_roster_view_dispose;
1306 oclass->finalize = empathy_roster_view_finalize;
1308 widget_class->button_press_event = empathy_roster_view_button_press_event;
1309 widget_class->key_press_event = empathy_roster_view_key_press_event;
1310 widget_class->query_tooltip = empathy_roster_view_query_tooltip;
1312 container_class->remove = empathy_roster_view_remove;
1314 box_class->child_activated = empathy_roster_view_child_activated;
1316 spec = g_param_spec_object ("model", "Model",
1317 "EmpathyRosterModel",
1318 EMPATHY_TYPE_ROSTER_MODEL,
1319 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1320 g_object_class_install_property (oclass, PROP_MODEL, spec);
1322 spec = g_param_spec_boolean ("show-offline", "Show Offline",
1323 "Show offline contacts",
1325 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1326 g_object_class_install_property (oclass, PROP_SHOW_OFFLINE, spec);
1328 spec = g_param_spec_boolean ("show-groups", "Show Groups",
1331 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
1332 g_object_class_install_property (oclass, PROP_SHOW_GROUPS, spec);
1334 spec = g_param_spec_boolean ("empty", "Empty",
1335 "Is the view currently empty?",
1337 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
1338 g_object_class_install_property (oclass, PROP_EMPTY, spec);
1340 signals[SIG_INDIVIDUAL_ACTIVATED] = g_signal_new ("individual-activated",
1341 G_OBJECT_CLASS_TYPE (klass),
1343 0, NULL, NULL, NULL,
1345 1, FOLKS_TYPE_INDIVIDUAL);
1347 signals[SIG_POPUP_INDIVIDUAL_MENU] = g_signal_new ("popup-individual-menu",
1348 G_OBJECT_CLASS_TYPE (klass),
1350 0, NULL, NULL, NULL,
1352 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_UINT, G_TYPE_UINT);
1354 signals[SIG_EVENT_ACTIVATED] = g_signal_new ("event-activated",
1355 G_OBJECT_CLASS_TYPE (klass),
1357 0, NULL, NULL, NULL,
1359 2, FOLKS_TYPE_INDIVIDUAL, G_TYPE_POINTER);
1361 signals[SIG_INDIVIDUAL_TOOLTIP] = g_signal_new ("individual-tooltip",
1362 G_OBJECT_CLASS_TYPE (klass),
1364 0, g_signal_accumulator_true_handled, NULL, NULL,
1366 3, FOLKS_TYPE_INDIVIDUAL, G_TYPE_BOOLEAN, GTK_TYPE_TOOLTIP);
1368 g_type_class_add_private (klass, sizeof (EmpathyRosterViewPriv));
1372 empathy_roster_view_init (EmpathyRosterView *self)
1374 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
1375 EMPATHY_TYPE_ROSTER_VIEW, EmpathyRosterViewPriv);
1377 self->priv->roster_contacts = g_hash_table_new_full (NULL, NULL,
1378 NULL, (GDestroyNotify) g_hash_table_unref);
1379 self->priv->roster_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
1381 self->priv->displayed_contacts = g_hash_table_new (NULL, NULL);
1383 self->priv->events = g_queue_new ();
1385 self->priv->empty = TRUE;
1389 empathy_roster_view_new (EmpathyRosterModel *model)
1391 g_return_val_if_fail (EMPATHY_IS_ROSTER_MODEL (model), NULL);
1393 return g_object_new (EMPATHY_TYPE_ROSTER_VIEW,
1399 empathy_roster_view_show_offline (EmpathyRosterView *self,
1402 if (self->priv->show_offline == show)
1405 self->priv->show_offline = show;
1406 egg_list_box_refilter (EGG_LIST_BOX (self));
1408 g_object_notify (G_OBJECT (self), "show-offline");
1412 empathy_roster_view_show_groups (EmpathyRosterView *self,
1415 if (self->priv->show_groups == show)
1418 self->priv->show_groups = show;
1420 /* TODO: block sort/filter? */
1422 populate_view (self);
1424 g_object_notify (G_OBJECT (self), "show-groups");
1428 select_first_contact (EmpathyRosterView *self)
1430 GList *children, *l;
1432 children = gtk_container_get_children (GTK_CONTAINER (self));
1433 for (l = children; l != NULL; l = g_list_next (l))
1435 GtkWidget *child = l->data;
1437 if (!gtk_widget_get_child_visible (child))
1440 if (!EMPATHY_IS_ROSTER_CONTACT (child))
1443 egg_list_box_select_child (EGG_LIST_BOX (self), child);
1447 g_list_free (children);
1451 search_timeout_cb (EmpathyRosterView *self)
1453 egg_list_box_refilter (EGG_LIST_BOX (self));
1455 select_first_contact (self);
1457 self->priv->search_id = 0;
1458 return G_SOURCE_REMOVE;
1462 search_text_notify_cb (EmpathyLiveSearch *search,
1464 EmpathyRosterView *self)
1466 if (self->priv->search_id != 0)
1467 g_source_remove (self->priv->search_id);
1469 self->priv->search_id = g_timeout_add (SEARCH_TIMEOUT,
1470 (GSourceFunc) search_timeout_cb, self);
1474 search_activate_cb (GtkWidget *search,
1475 EmpathyRosterView *self)
1477 EggListBox *box = EGG_LIST_BOX (self);
1480 child = egg_list_box_get_selected_child (box);
1484 empathy_roster_view_child_activated (box, child);
1488 empathy_roster_view_set_live_search (EmpathyRosterView *self,
1489 EmpathyLiveSearch *search)
1491 if (self->priv->search != NULL)
1493 g_signal_handlers_disconnect_by_func (self->priv->search,
1494 search_text_notify_cb, self);
1495 g_signal_handlers_disconnect_by_func (self->priv->search,
1496 search_activate_cb, self);
1498 g_clear_object (&self->priv->search);
1504 self->priv->search = g_object_ref (search);
1506 g_signal_connect (self->priv->search, "notify::text",
1507 G_CALLBACK (search_text_notify_cb), self);
1508 g_signal_connect (self->priv->search, "activate",
1509 G_CALLBACK (search_activate_cb), self);
1513 empathy_roster_view_is_empty (EmpathyRosterView *self)
1515 return self->priv->empty;
1519 empathy_roster_view_is_searching (EmpathyRosterView *self)
1521 return (self->priv->search != NULL &&
1522 gtk_widget_get_visible (GTK_WIDGET (self->priv->search)));
1525 /* Don't use EmpathyEvent as I prefer to keep this object not too specific to
1526 * Empathy's internals. */
1528 empathy_roster_view_add_event (EmpathyRosterView *self,
1529 FolksIndividual *individual,
1533 GHashTable *contacts;
1535 contacts = g_hash_table_lookup (self->priv->roster_contacts, individual);
1536 if (contacts == NULL)
1539 self->priv->last_event_id++;
1541 g_queue_push_head (self->priv->events,
1542 event_new (self->priv->last_event_id, individual, icon, user_data));
1544 start_flashing (self);
1546 return self->priv->last_event_id;
1550 empathy_roster_view_remove_event (EmpathyRosterView *self,
1555 for (l = g_queue_peek_head_link (self->priv->events); l != NULL;
1556 l = g_list_next (l))
1558 Event *event = l->data;
1560 if (event->id == event_id)
1562 remove_event (self, event);
1569 empathy_roster_view_get_selected_individual (EmpathyRosterView *self)
1573 child = egg_list_box_get_selected_child (EGG_LIST_BOX (self));
1575 if (!EMPATHY_IS_ROSTER_CONTACT (child))
1578 return empathy_roster_contact_get_individual (EMPATHY_ROSTER_CONTACT (child));