1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Martyn Russell <martyn@imendio.com>
28 #include <glib/gi18n.h>
30 #include <glade/glade.h>
32 #include <libmissioncontrol/mc-account.h>
33 #include <libmissioncontrol/mission-control.h>
35 #include <libempathy/empathy-contact-manager.h>
36 #include <libempathy/gossip-debug.h>
37 #include <libempathy/gossip-utils.h>
39 #include "empathy-images.h"
40 #include "gossip-contact-list.h"
41 #include "gossip-contact-groups.h"
42 #include "gossip-cell-renderer-expander.h"
43 #include "gossip-cell-renderer-text.h"
44 #include "gossip-ui-utils.h"
45 //#include "gossip-chat-invite.h"
46 //#include "gossip-contact-info-dialog.h"
47 //#include "gossip-edit-contact-dialog.h"
48 //#include "gossip-ft-window.h"
49 //#include "gossip-log-window.h"
51 #define DEBUG_DOMAIN "ContactListUI"
53 /* Flashing delay for icons (milliseconds). */
54 #define FLASH_TIMEOUT 500
56 /* Active users are those which have recently changed state
57 * (e.g. online, offline or from normal to a busy state).
60 /* Time user is shown as active */
61 #define ACTIVE_USER_SHOW_TIME 7000
63 /* Time after connecting which we wait before active users are enabled */
64 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
66 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
68 struct _GossipContactListPriv {
69 EmpathyContactManager *manager;
74 GtkTreeRowReference *drag_row;
80 gboolean show_offline;
81 gboolean show_avatars;
85 GossipContactListSort sort_criterium;
95 GossipContact *contact;
101 GossipContactList *list;
107 GossipContactList *list;
108 GossipContact *contact;
112 static void gossip_contact_list_class_init (GossipContactListClass *klass);
113 static void gossip_contact_list_init (GossipContactList *list);
114 static void contact_list_finalize (GObject *object);
115 static void contact_list_get_property (GObject *object,
119 static void contact_list_set_property (GObject *object,
123 static gboolean contact_list_row_separator_func (GtkTreeModel *model,
126 static void contact_list_contact_update (GossipContactList *list,
127 GossipContact *contact);
128 static void contact_list_contact_added_cb (EmpathyContactManager *manager,
129 GossipContact *contact,
130 GossipContactList *list);
131 static void contact_list_contact_updated_cb (GossipContact *contact,
133 GossipContactList *list);
134 static void contact_list_contact_groups_updated_cb (GossipContact *contact,
136 GossipContactList *list);
137 static void contact_list_contact_removed_cb (EmpathyContactManager *manager,
138 GossipContact *contact,
139 GossipContactList *list);
140 static void contact_list_contact_set_active (GossipContactList *list,
141 GossipContact *contact,
143 gboolean set_changed);
144 static ShowActiveData *
145 contact_list_contact_active_new (GossipContactList *list,
146 GossipContact *contact,
148 static void contact_list_contact_active_free (ShowActiveData *data);
149 static gboolean contact_list_contact_active_cb (ShowActiveData *data);
150 static gchar * contact_list_get_parent_group (GtkTreeModel *model,
152 gboolean *path_is_group);
153 static void contact_list_get_group (GossipContactList *list,
155 GtkTreeIter *iter_group_to_set,
156 GtkTreeIter *iter_separator_to_set,
158 static gboolean contact_list_get_group_foreach (GtkTreeModel *model,
162 static void contact_list_add_contact (GossipContactList *list,
163 GossipContact *contact);
164 static void contact_list_remove_contact (GossipContactList *list,
165 GossipContact *contact);
166 static void contact_list_create_model (GossipContactList *list);
167 static gboolean contact_list_search_equal_func (GtkTreeModel *model,
171 gpointer search_data);
172 static void contact_list_setup_view (GossipContactList *list);
173 static void contact_list_drag_data_received (GtkWidget *widget,
174 GdkDragContext *context,
177 GtkSelectionData *selection,
181 static gboolean contact_list_drag_motion (GtkWidget *widget,
182 GdkDragContext *context,
187 static gboolean contact_list_drag_motion_cb (DragMotionData *data);
188 static void contact_list_drag_begin (GtkWidget *widget,
189 GdkDragContext *context,
191 static void contact_list_drag_data_get (GtkWidget *widget,
192 GdkDragContext *contact,
193 GtkSelectionData *selection,
197 static void contact_list_drag_end (GtkWidget *widget,
198 GdkDragContext *context,
200 static void contact_list_cell_set_background (GossipContactList *list,
201 GtkCellRenderer *cell,
204 static void contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
205 GtkCellRenderer *cell,
208 GossipContactList *list);
209 static void contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
210 GtkCellRenderer *cell,
213 GossipContactList *list);
214 static void contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
215 GtkCellRenderer *cell,
218 GossipContactList *list);
219 static void contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
220 GtkCellRenderer *cell,
223 GossipContactList *list);
224 static GtkWidget *contact_list_get_contact_menu (GossipContactList *list,
225 gboolean can_send_file,
226 gboolean can_show_log);
227 static gboolean contact_list_button_press_event_cb (GossipContactList *list,
228 GdkEventButton *event,
230 static void contact_list_row_activated_cb (GossipContactList *list,
232 GtkTreeViewColumn *col,
234 static void contact_list_row_expand_or_collapse_cb (GossipContactList *list,
238 static gint contact_list_name_sort_func (GtkTreeModel *model,
242 static gint contact_list_state_sort_func (GtkTreeModel *model,
246 static gboolean contact_list_filter_func (GtkTreeModel *model,
248 GossipContactList *list);
249 static GList * contact_list_find_contact (GossipContactList *list,
250 GossipContact *contact);
251 static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
255 static void contact_list_action_cb (GtkAction *action,
256 GossipContactList *list);
257 static void contact_list_action_activated (GossipContactList *list,
258 GossipContact *contact);
259 static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
262 GossipContactList *list);
267 COL_PIXBUF_AVATAR_VISIBLE,
288 static const GtkActionEntry entries[] = {
289 { "ContactMenu", NULL,
290 N_("_Contact"), NULL, NULL,
294 N_("_Group"),NULL, NULL,
297 { "Chat", EMPATHY_IMAGE_MESSAGE,
298 N_("_Chat"), NULL, N_("Chat with contact"),
299 G_CALLBACK (contact_list_action_cb)
301 { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
302 N_("Infor_mation"), "<control>I", N_("View contact information"),
303 G_CALLBACK (contact_list_action_cb)
306 N_("Re_name"), NULL, N_("Rename"),
307 G_CALLBACK (contact_list_action_cb)
309 { "Edit", GTK_STOCK_EDIT,
310 N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
311 G_CALLBACK (contact_list_action_cb)
313 { "Remove", GTK_STOCK_REMOVE,
314 N_("_Remove"), NULL, N_("Remove contact"),
315 G_CALLBACK (contact_list_action_cb)
317 { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
318 N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
319 G_CALLBACK (contact_list_action_cb)
322 N_("_Send File..."), NULL, N_("Send a file"),
323 G_CALLBACK (contact_list_action_cb)
325 { "Log", GTK_STOCK_JUSTIFY_LEFT,
326 N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
327 G_CALLBACK (contact_list_action_cb)
331 static guint n_entries = G_N_ELEMENTS (entries);
333 static const gchar *ui_info =
335 " <popup name='Contact'>"
336 " <menuitem action='Chat'/>"
337 " <menuitem action='Log'/>"
338 " <menuitem action='SendFile'/>"
340 " <menuitem action='Invite'/>"
342 " <menuitem action='Edit'/>"
343 " <menuitem action='Remove'/>"
345 " <menuitem action='Information'/>"
347 " <popup name='Group'>"
348 " <menuitem action='Rename'/>"
353 DND_DRAG_TYPE_CONTACT_ID,
355 DND_DRAG_TYPE_STRING,
358 static const GtkTargetEntry drag_types_dest[] = {
359 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
360 { "text/uri-list", 0, DND_DRAG_TYPE_URL },
361 { "text/plain", 0, DND_DRAG_TYPE_STRING },
362 { "STRING", 0, DND_DRAG_TYPE_STRING },
365 static const GtkTargetEntry drag_types_source[] = {
366 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
369 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
370 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
373 gossip_contact_list_sort_get_type (void)
375 static GType etype = 0;
378 static const GEnumValue values[] = {
379 { GOSSIP_CONTACT_LIST_SORT_NAME,
380 "GOSSIP_CONTACT_LIST_SORT_NAME",
382 { GOSSIP_CONTACT_LIST_SORT_STATE,
383 "GOSSIP_CONTACT_LIST_SORT_STATE",
388 etype = g_enum_register_static ("GossipContactListSort", values);
394 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
397 gossip_contact_list_class_init (GossipContactListClass *klass)
399 GObjectClass *object_class = G_OBJECT_CLASS (klass);
401 object_class->finalize = contact_list_finalize;
402 object_class->get_property = contact_list_get_property;
403 object_class->set_property = contact_list_set_property;
405 g_object_class_install_property (object_class,
407 g_param_spec_boolean ("show-offline",
409 "Whether contact list should display "
413 g_object_class_install_property (object_class,
415 g_param_spec_boolean ("show-avatars",
417 "Whether contact list should display "
418 "avatars for contacts",
421 g_object_class_install_property (object_class,
423 g_param_spec_boolean ("is-compact",
425 "Whether the contact list is in compact mode or not",
429 g_object_class_install_property (object_class,
431 g_param_spec_string ("filter",
433 "The text to use to filter the contact list",
437 g_object_class_install_property (object_class,
439 g_param_spec_enum ("sort-criterium",
441 "The sort criterium to use for sorting the contact list",
442 GOSSIP_TYPE_CONTACT_LIST_SORT,
443 GOSSIP_CONTACT_LIST_SORT_NAME,
446 g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
450 gossip_contact_list_init (GossipContactList *list)
452 GossipContactListPriv *priv;
453 GtkActionGroup *action_group;
455 GError *error = NULL;
457 priv = GET_PRIV (list);
459 priv->manager = empathy_contact_manager_new ();
460 priv->is_compact = FALSE;
461 priv->show_active = TRUE;
462 priv->show_avatars = TRUE;
464 contact_list_create_model (list);
465 contact_list_setup_view (list);
466 empathy_contact_manager_setup (priv->manager);
468 /* Get saved group states. */
469 gossip_contact_groups_get_all ();
471 /* Set up UI Manager */
472 priv->ui = gtk_ui_manager_new ();
474 action_group = gtk_action_group_new ("Actions");
475 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
476 gtk_action_group_add_actions (action_group, entries, n_entries, list);
477 gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
479 if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
480 g_warning ("Could not build contact menus from string:'%s'", error->message);
481 g_error_free (error);
484 g_object_unref (action_group);
486 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list),
487 contact_list_row_separator_func,
490 /* Signal connection. */
491 g_signal_connect (priv->manager,
493 G_CALLBACK (contact_list_contact_added_cb),
495 g_signal_connect (priv->manager,
497 G_CALLBACK (contact_list_contact_removed_cb),
500 /* Connect to tree view signals rather than override. */
501 g_signal_connect (list,
502 "button-press-event",
503 G_CALLBACK (contact_list_button_press_event_cb),
505 g_signal_connect (list,
507 G_CALLBACK (contact_list_row_activated_cb),
509 g_signal_connect (list,
511 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
512 GINT_TO_POINTER (TRUE));
513 g_signal_connect (list,
515 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
516 GINT_TO_POINTER (FALSE));
518 /* Add contacts already created */
519 contacts = empathy_contact_manager_get_contacts (priv->manager);
520 for (l = contacts; l; l = l->next) {
521 GossipContact *contact;
525 contact_list_contact_added_cb (priv->manager, contact, list);
527 g_object_unref (contact);
529 g_list_free (contacts);
533 contact_list_finalize (GObject *object)
535 GossipContactListPriv *priv;
537 priv = GET_PRIV (object);
539 /* FIXME: disconnect all signals on the manager and contacts */
541 g_object_unref (priv->manager);
542 g_object_unref (priv->ui);
543 g_object_unref (priv->store);
544 g_object_unref (priv->filter);
545 g_free (priv->filter_text);
547 G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
551 contact_list_get_property (GObject *object,
556 GossipContactListPriv *priv;
558 priv = GET_PRIV (object);
561 case PROP_SHOW_OFFLINE:
562 g_value_set_boolean (value, priv->show_offline);
564 case PROP_SHOW_AVATARS:
565 g_value_set_boolean (value, priv->show_avatars);
567 case PROP_IS_COMPACT:
568 g_value_set_boolean (value, priv->is_compact);
571 g_value_set_string (value, priv->filter_text);
573 case PROP_SORT_CRITERIUM:
574 g_value_set_enum (value, priv->sort_criterium);
577 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
583 contact_list_set_property (GObject *object,
588 GossipContactListPriv *priv;
590 priv = GET_PRIV (object);
593 case PROP_SHOW_OFFLINE:
594 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
595 g_value_get_boolean (value));
597 case PROP_SHOW_AVATARS:
598 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
599 g_value_get_boolean (value));
601 case PROP_IS_COMPACT:
602 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
603 g_value_get_boolean (value));
606 gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
607 g_value_get_string (value));
609 case PROP_SORT_CRITERIUM:
610 gossip_contact_list_set_sort_criterium (GOSSIP_CONTACT_LIST (object),
611 g_value_get_enum (value));
614 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
620 contact_list_row_separator_func (GtkTreeModel *model,
624 gboolean is_separator = FALSE;
626 gtk_tree_model_get (model, iter,
627 COL_IS_SEPARATOR, &is_separator,
634 contact_list_contact_update (GossipContactList *list,
635 GossipContact *contact)
637 GossipContactListPriv *priv;
638 ShowActiveData *data;
642 gboolean should_be_in_list;
643 gboolean was_online = TRUE;
644 gboolean now_online = FALSE;
645 gboolean set_model = FALSE;
646 gboolean do_remove = FALSE;
647 gboolean do_set_active = FALSE;
648 gboolean do_set_refresh = FALSE;
649 GdkPixbuf *pixbuf_avatar;
651 priv = GET_PRIV (list);
653 model = GTK_TREE_MODEL (priv->store);
655 iters = contact_list_find_contact (list, contact);
662 /* Get online state now. */
663 now_online = gossip_contact_is_online (contact);
665 if (priv->show_offline || now_online) {
666 should_be_in_list = TRUE;
668 should_be_in_list = FALSE;
671 if (!in_list && !should_be_in_list) {
673 gossip_debug (DEBUG_DOMAIN,
674 "Contact:'%s' in list:NO, should be:NO",
675 gossip_contact_get_name (contact));
677 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
681 else if (in_list && !should_be_in_list) {
682 gossip_debug (DEBUG_DOMAIN,
683 "Contact:'%s' in list:YES, should be:NO",
684 gossip_contact_get_name (contact));
686 if (priv->show_active) {
688 do_set_active = TRUE;
689 do_set_refresh = TRUE;
692 gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
694 gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
695 contact_list_remove_contact (list, contact);
698 else if (!in_list && should_be_in_list) {
699 gossip_debug (DEBUG_DOMAIN,
700 "Contact:'%s' in list:NO, should be:YES",
701 gossip_contact_get_name (contact));
703 contact_list_add_contact (list, contact);
705 if (priv->show_active) {
706 do_set_active = TRUE;
708 gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
711 gossip_debug (DEBUG_DOMAIN,
712 "Contact:'%s' in list:YES, should be:YES",
713 gossip_contact_get_name (contact));
715 /* Get online state before. */
716 if (iters && g_list_length (iters) > 0) {
717 gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
720 /* Is this really an update or an online/offline. */
721 if (priv->show_active) {
722 if (was_online != now_online) {
725 do_set_active = TRUE;
726 do_set_refresh = TRUE;
729 str = "online -> offline";
731 str = "offline -> online";
734 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
736 /* Was TRUE for presence updates. */
737 /* do_set_active = FALSE; */
738 do_set_refresh = TRUE;
740 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
747 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
748 for (l = iters; l && set_model; l = l->next) {
749 gtk_tree_store_set (priv->store, l->data,
750 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
751 COL_STATUS, gossip_contact_get_status (contact),
752 COL_IS_ONLINE, now_online,
753 COL_NAME, gossip_contact_get_name (contact),
754 COL_PIXBUF_AVATAR, pixbuf_avatar,
759 g_object_unref (pixbuf_avatar);
762 if (priv->show_active && do_set_active) {
763 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
766 data = contact_list_contact_active_new (list, contact, do_remove);
767 g_timeout_add (ACTIVE_USER_SHOW_TIME,
768 (GSourceFunc) contact_list_contact_active_cb,
773 /* FIXME: when someone goes online then offline quickly, the
774 * first timeout sets the user to be inactive and the second
775 * timeout removes the user from the contact list, really we
776 * should remove the first timeout.
778 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
783 contact_list_contact_added_cb (EmpathyContactManager *manager,
784 GossipContact *contact,
785 GossipContactList *list)
787 GossipContactListPriv *priv;
789 priv = GET_PRIV (list);
791 gossip_debug (DEBUG_DOMAIN,
792 "Contact:'%s' added",
793 gossip_contact_get_name (contact));
795 g_signal_connect (contact, "notify::groups",
796 G_CALLBACK (contact_list_contact_groups_updated_cb),
798 g_signal_connect (contact, "notify::presence",
799 G_CALLBACK (contact_list_contact_updated_cb),
801 g_signal_connect (contact, "notify::name",
802 G_CALLBACK (contact_list_contact_updated_cb),
804 g_signal_connect (contact, "notify::avatar",
805 G_CALLBACK (contact_list_contact_updated_cb),
807 g_signal_connect (contact, "notify::type",
808 G_CALLBACK (contact_list_contact_updated_cb),
811 contact_list_add_contact (list, contact);
815 contact_list_contact_groups_updated_cb (GossipContact *contact,
817 GossipContactList *list)
819 GossipContactListPriv *priv;
821 priv = GET_PRIV (list);
823 if (priv->show_offline || gossip_contact_is_online (contact)) {
827 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
828 gossip_contact_get_name (contact));
830 /* We do this to make sure the groups are correct, if not, we
831 * would have to check the groups already set up for each
832 * contact and then see what has been updated.
834 contact_list_remove_contact (list, contact);
835 contact_list_add_contact (list, contact);
839 contact_list_contact_updated_cb (GossipContact *contact,
841 GossipContactList *list)
843 gossip_debug (DEBUG_DOMAIN,
844 "Contact:'%s' updated, checking roster is in sync...",
845 gossip_contact_get_name (contact));
847 contact_list_contact_update (list, contact);
851 contact_list_contact_removed_cb (EmpathyContactManager *manager,
852 GossipContact *contact,
853 GossipContactList *list)
855 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
856 gossip_contact_get_name (contact));
858 /* Disconnect signals */
859 g_signal_handlers_disconnect_by_func (contact,
860 G_CALLBACK (contact_list_contact_groups_updated_cb),
862 g_signal_handlers_disconnect_by_func (contact,
863 G_CALLBACK (contact_list_contact_updated_cb),
866 contact_list_remove_contact (list, contact);
870 contact_list_contact_set_active (GossipContactList *list,
871 GossipContact *contact,
873 gboolean set_changed)
875 GossipContactListPriv *priv;
879 priv = GET_PRIV (list);
881 model = GTK_TREE_MODEL (priv->store);
883 iters = contact_list_find_contact (list, contact);
884 for (l = iters; l; l = l->next) {
887 gtk_tree_store_set (priv->store, l->data,
888 COL_IS_ACTIVE, active,
891 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
894 path = gtk_tree_model_get_path (model, l->data);
895 gtk_tree_model_row_changed (model, path, l->data);
896 gtk_tree_path_free (path);
900 g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
905 static ShowActiveData *
906 contact_list_contact_active_new (GossipContactList *list,
907 GossipContact *contact,
910 ShowActiveData *data;
912 g_return_val_if_fail (list != NULL, NULL);
913 g_return_val_if_fail (contact != NULL, NULL);
915 gossip_debug (DEBUG_DOMAIN,
916 "Contact:'%s' now active, and %s be removed",
917 gossip_contact_get_name (contact),
918 remove ? "WILL" : "WILL NOT");
920 data = g_slice_new0 (ShowActiveData);
922 data->list = g_object_ref (list);
923 data->contact = g_object_ref (contact);
925 data->remove = remove;
931 contact_list_contact_active_free (ShowActiveData *data)
933 g_return_if_fail (data != NULL);
935 g_object_unref (data->contact);
936 g_object_unref (data->list);
938 g_slice_free (ShowActiveData, data);
942 contact_list_contact_active_cb (ShowActiveData *data)
944 GossipContactListPriv *priv;
946 g_return_val_if_fail (data != NULL, FALSE);
948 priv = GET_PRIV (data->list);
951 !priv->show_offline &&
952 !gossip_contact_is_online (data->contact)) {
953 gossip_debug (DEBUG_DOMAIN,
954 "Contact:'%s' active timeout, removing item",
955 gossip_contact_get_name (data->contact));
956 contact_list_remove_contact (data->list,
960 gossip_debug (DEBUG_DOMAIN,
961 "Contact:'%s' no longer active",
962 gossip_contact_get_name (data->contact));
963 contact_list_contact_set_active (data->list,
968 contact_list_contact_active_free (data);
974 contact_list_get_parent_group (GtkTreeModel *model,
976 gboolean *path_is_group)
978 GtkTreeIter parent_iter, iter;
982 g_return_val_if_fail (model != NULL, NULL);
983 g_return_val_if_fail (path != NULL, NULL);
984 g_return_val_if_fail (path_is_group != NULL, NULL);
986 if (!gtk_tree_model_get_iter (model, &iter, path)) {
990 gtk_tree_model_get (model, &iter,
991 COL_IS_GROUP, &is_group,
995 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
1001 gtk_tree_model_get (model, &iter,
1002 COL_IS_GROUP, &is_group,
1009 *path_is_group = TRUE;
1012 gtk_tree_model_get (model, &iter,
1020 contact_list_get_group_foreach (GtkTreeModel *model,
1028 /* Groups are only at the top level. */
1029 if (gtk_tree_path_get_depth (path) != 1) {
1033 gtk_tree_model_get (model, iter,
1035 COL_IS_GROUP, &is_group,
1038 if (is_group && strcmp (str, fg->name) == 0) {
1049 contact_list_get_group (GossipContactList *list,
1051 GtkTreeIter *iter_group_to_set,
1052 GtkTreeIter *iter_separator_to_set,
1055 GossipContactListPriv *priv;
1056 GtkTreeModel *model;
1057 GtkTreeIter iter_group, iter_separator;
1060 priv = GET_PRIV (list);
1062 memset (&fg, 0, sizeof (fg));
1066 model = GTK_TREE_MODEL (priv->store);
1067 gtk_tree_model_foreach (model,
1068 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1076 gtk_tree_store_append (priv->store, &iter_group, NULL);
1077 gtk_tree_store_set (priv->store, &iter_group,
1078 COL_ICON_STATUS, NULL,
1081 COL_IS_ACTIVE, FALSE,
1082 COL_IS_SEPARATOR, FALSE,
1085 if (iter_group_to_set) {
1086 *iter_group_to_set = iter_group;
1089 gtk_tree_store_append (priv->store,
1092 gtk_tree_store_set (priv->store, &iter_separator,
1093 COL_IS_SEPARATOR, TRUE,
1096 if (iter_separator_to_set) {
1097 *iter_separator_to_set = iter_separator;
1104 if (iter_group_to_set) {
1105 *iter_group_to_set = fg.iter;
1108 iter_separator = fg.iter;
1110 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1111 gboolean is_separator;
1113 gtk_tree_model_get (model, &iter_separator,
1114 COL_IS_SEPARATOR, &is_separator,
1117 if (is_separator && iter_separator_to_set) {
1118 *iter_separator_to_set = iter_separator;
1125 contact_list_add_contact (GossipContactList *list,
1126 GossipContact *contact)
1128 GossipContactListPriv *priv;
1129 GtkTreeIter iter, iter_group, iter_separator;
1130 GtkTreeModel *model;
1133 priv = GET_PRIV (list);
1135 if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1139 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1141 /* If no groups just add it at the top level. */
1142 groups = gossip_contact_get_groups (contact);
1144 GdkPixbuf *pixbuf_avatar;
1145 gboolean show_avatar = FALSE;
1147 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1149 if (priv->show_avatars && !priv->is_compact) {
1153 gossip_debug (DEBUG_DOMAIN, "");
1154 gossip_debug (DEBUG_DOMAIN,
1155 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1157 gossip_debug (DEBUG_DOMAIN,
1158 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
1160 G_IS_OBJECT (contact) ? "yes" : "no",
1161 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1163 gtk_tree_store_append (priv->store, &iter, NULL);
1164 gtk_tree_store_set (priv->store, &iter,
1165 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1166 COL_PIXBUF_AVATAR, pixbuf_avatar,
1167 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1168 COL_NAME, gossip_contact_get_name (contact),
1169 COL_STATUS, gossip_contact_get_status (contact),
1170 COL_STATUS_VISIBLE, !priv->is_compact,
1171 COL_CONTACT, contact,
1172 COL_IS_GROUP, FALSE,
1173 COL_IS_ACTIVE, FALSE,
1174 COL_IS_ONLINE, gossip_contact_is_online (contact),
1175 COL_IS_SEPARATOR, FALSE,
1178 gossip_debug (DEBUG_DOMAIN,
1179 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1180 gossip_debug (DEBUG_DOMAIN, "");
1182 if (pixbuf_avatar) {
1183 g_object_unref (pixbuf_avatar);
1187 /* Else add to each group. */
1188 for (l = groups; l; l = l->next) {
1190 GtkTreeIter model_iter_group;
1191 GdkPixbuf *pixbuf_avatar;
1195 gboolean show_avatar = FALSE;
1202 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1204 contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
1206 if (priv->show_avatars && !priv->is_compact) {
1210 gossip_debug (DEBUG_DOMAIN, "");
1211 gossip_debug (DEBUG_DOMAIN,
1212 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1214 gossip_debug (DEBUG_DOMAIN,
1215 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
1217 G_IS_OBJECT (contact) ? "yes" : "no",
1218 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1220 gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
1221 gtk_tree_store_set (priv->store, &iter,
1222 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1223 COL_PIXBUF_AVATAR, pixbuf_avatar,
1224 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1225 COL_NAME, gossip_contact_get_name (contact),
1226 COL_STATUS, gossip_contact_get_status (contact),
1227 COL_STATUS_VISIBLE, !priv->is_compact,
1228 COL_CONTACT, contact,
1229 COL_IS_GROUP, FALSE,
1230 COL_IS_ACTIVE, FALSE,
1231 COL_IS_ONLINE, gossip_contact_is_online (contact),
1232 COL_IS_SEPARATOR, FALSE,
1235 gossip_debug (DEBUG_DOMAIN,
1236 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1237 gossip_debug (DEBUG_DOMAIN, "");
1239 if (pixbuf_avatar) {
1240 g_object_unref (pixbuf_avatar);
1247 found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),
1254 path = gtk_tree_model_get_path (model, &model_iter_group);
1259 if (gossip_contact_group_get_expanded (name)) {
1260 g_signal_handlers_block_by_func (list,
1261 contact_list_row_expand_or_collapse_cb,
1262 GINT_TO_POINTER (TRUE));
1263 gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1264 g_signal_handlers_unblock_by_func (list,
1265 contact_list_row_expand_or_collapse_cb,
1266 GINT_TO_POINTER (TRUE));
1268 g_signal_handlers_block_by_func (list,
1269 contact_list_row_expand_or_collapse_cb,
1270 GINT_TO_POINTER (FALSE));
1271 gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1272 g_signal_handlers_unblock_by_func (list,
1273 contact_list_row_expand_or_collapse_cb,
1274 GINT_TO_POINTER (FALSE));
1277 gtk_tree_path_free (path);
1282 contact_list_remove_contact (GossipContactList *list,
1283 GossipContact *contact)
1285 GossipContactListPriv *priv;
1286 GtkTreeModel *model;
1289 priv = GET_PRIV (list);
1291 iters = contact_list_find_contact (list, contact);
1296 /* Clean up model */
1297 model = GTK_TREE_MODEL (priv->store);
1299 for (l = iters; l; l = l->next) {
1302 /* NOTE: it is only <= 2 here because we have
1303 * separators after the group name, otherwise it
1306 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1307 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1308 gtk_tree_store_remove (priv->store, &parent);
1310 gtk_tree_store_remove (priv->store, l->data);
1314 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1315 g_list_free (iters);
1319 contact_list_create_model (GossipContactList *list)
1321 GossipContactListPriv *priv;
1322 GtkTreeModel *model;
1324 priv = GET_PRIV (list);
1327 g_object_unref (priv->store);
1331 g_object_unref (priv->filter);
1334 priv->store = gtk_tree_store_new (COL_COUNT,
1335 G_TYPE_STRING, /* Status icon-name */
1336 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
1337 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
1338 G_TYPE_STRING, /* Name */
1339 G_TYPE_STRING, /* Status string */
1340 G_TYPE_BOOLEAN, /* Show status */
1341 GOSSIP_TYPE_CONTACT, /* Contact type */
1342 G_TYPE_BOOLEAN, /* Is group */
1343 G_TYPE_BOOLEAN, /* Is active */
1344 G_TYPE_BOOLEAN, /* Is online */
1345 G_TYPE_BOOLEAN); /* Is separator */
1347 /* Save normal model */
1348 model = GTK_TREE_MODEL (priv->store);
1350 /* Set up sorting */
1351 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1353 contact_list_name_sort_func,
1355 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1357 contact_list_state_sort_func,
1360 gossip_contact_list_set_sort_criterium (list, priv->sort_criterium);
1363 priv->filter = gtk_tree_model_filter_new (model, NULL);
1365 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
1366 (GtkTreeModelFilterVisibleFunc)
1367 contact_list_filter_func,
1370 gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
1374 contact_list_search_equal_func (GtkTreeModel *model,
1378 gpointer search_data)
1380 gchar *name, *name_folded;
1388 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1394 name_folded = g_utf8_casefold (name, -1);
1395 key_folded = g_utf8_casefold (key, -1);
1397 if (name_folded && key_folded &&
1398 strstr (name_folded, key_folded)) {
1405 g_free (name_folded);
1406 g_free (key_folded);
1412 contact_list_setup_view (GossipContactList *list)
1414 GtkCellRenderer *cell;
1415 GtkTreeViewColumn *col;
1418 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1419 contact_list_search_equal_func,
1424 "headers-visible", FALSE,
1425 "reorderable", TRUE,
1426 "show-expanders", FALSE,
1429 col = gtk_tree_view_column_new ();
1432 cell = gtk_cell_renderer_pixbuf_new ();
1433 gtk_tree_view_column_pack_start (col, cell, FALSE);
1434 gtk_tree_view_column_set_cell_data_func (
1436 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1446 cell = gossip_cell_renderer_text_new ();
1447 gtk_tree_view_column_pack_start (col, cell, TRUE);
1448 gtk_tree_view_column_set_cell_data_func (
1450 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1453 gtk_tree_view_column_add_attribute (col, cell,
1455 gtk_tree_view_column_add_attribute (col, cell,
1456 "status", COL_STATUS);
1457 gtk_tree_view_column_add_attribute (col, cell,
1458 "is_group", COL_IS_GROUP);
1461 cell = gtk_cell_renderer_pixbuf_new ();
1462 gtk_tree_view_column_pack_start (col, cell, FALSE);
1463 gtk_tree_view_column_set_cell_data_func (
1465 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1477 cell = gossip_cell_renderer_expander_new ();
1478 gtk_tree_view_column_pack_end (col, cell, FALSE);
1479 gtk_tree_view_column_set_cell_data_func (
1481 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1484 /* Actually add the column now we have added all cell renderers */
1485 gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1488 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1489 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1493 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1494 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1498 /* Note: We support the COPY action too, but need to make the
1499 * MOVE action the default.
1501 gtk_drag_source_set (GTK_WIDGET (list),
1504 G_N_ELEMENTS (drag_types_source),
1507 gtk_drag_dest_set (GTK_WIDGET (list),
1508 GTK_DEST_DEFAULT_ALL,
1510 G_N_ELEMENTS (drag_types_dest),
1511 GDK_ACTION_MOVE | GDK_ACTION_LINK);
1513 g_signal_connect (GTK_WIDGET (list),
1514 "drag-data-received",
1515 G_CALLBACK (contact_list_drag_data_received),
1518 /* FIXME: noticed but when you drag the row over the treeview
1519 * fast, it seems to stop redrawing itself, if we don't
1520 * connect this signal, all is fine.
1522 g_signal_connect (GTK_WIDGET (list),
1524 G_CALLBACK (contact_list_drag_motion),
1527 g_signal_connect (GTK_WIDGET (list),
1529 G_CALLBACK (contact_list_drag_begin),
1531 g_signal_connect (GTK_WIDGET (list),
1533 G_CALLBACK (contact_list_drag_data_get),
1535 g_signal_connect (GTK_WIDGET (list),
1537 G_CALLBACK (contact_list_drag_end),
1542 contact_list_drag_data_received (GtkWidget *widget,
1543 GdkDragContext *context,
1546 GtkSelectionData *selection,
1551 GossipContactListPriv *priv;
1552 GtkTreeModel *model;
1554 GtkTreeViewDropPosition position;
1555 GossipContact *contact;
1560 gboolean drag_success = TRUE;
1561 gboolean drag_del = FALSE;
1563 priv = GET_PRIV (widget);
1565 id = (const gchar*) selection->data;
1566 gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1567 context->action == GDK_ACTION_MOVE ? "move" : "",
1568 context->action == GDK_ACTION_COPY ? "copy" : "",
1571 /* FIXME: This is ambigous, an id can come from multiple accounts */
1572 contact = empathy_contact_manager_find (priv->manager, id);
1574 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1578 groups = gossip_contact_get_groups (contact);
1580 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1587 if (g_list_length (groups) != 1) {
1588 /* if they have dragged a contact out of a
1589 * group then we would set the contact to have
1590 * NO groups but only if they were ONE group
1591 * to begin with - should we do this
1592 * regardless to how many groups they are in
1593 * already or not at all?
1598 gossip_contact_set_groups (contact, NULL);
1600 GList *l, *new_groups;
1604 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1605 name = contact_list_get_parent_group (model, path, &is_group);
1607 if (groups && name &&
1608 g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1613 /* Get source group information. */
1614 priv = GET_PRIV (widget);
1615 if (!priv->drag_row) {
1620 path = gtk_tree_row_reference_get_path (priv->drag_row);
1626 old_group = contact_list_get_parent_group (model, path, &is_group);
1627 gtk_tree_path_free (path);
1629 if (!name && old_group && GDK_ACTION_MOVE) {
1630 drag_success = FALSE;
1633 if (context->action == GDK_ACTION_MOVE) {
1637 /* Create new groups GList. */
1638 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1642 if (context->action == GDK_ACTION_MOVE &&
1643 old_group != NULL &&
1644 strcmp (str, old_group) == 0) {
1652 new_groups = g_list_append (new_groups, g_strdup (str));
1657 new_groups = g_list_append (new_groups, name);
1659 gossip_contact_set_groups (contact, new_groups);
1665 gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1669 contact_list_drag_motion (GtkWidget *widget,
1670 GdkDragContext *context,
1676 static DragMotionData *dm = NULL;
1679 gboolean is_different = FALSE;
1680 gboolean cleanup = TRUE;
1682 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1693 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1694 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1699 if (!is_different && !cleanup) {
1704 gtk_tree_path_free (dm->path);
1705 if (dm->timeout_id) {
1706 g_source_remove (dm->timeout_id);
1714 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1715 dm = g_new0 (DragMotionData, 1);
1717 dm->list = GOSSIP_CONTACT_LIST (widget);
1718 dm->path = gtk_tree_path_copy (path);
1720 dm->timeout_id = g_timeout_add (
1722 (GSourceFunc) contact_list_drag_motion_cb,
1730 contact_list_drag_motion_cb (DragMotionData *data)
1732 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1736 data->timeout_id = 0;
1742 contact_list_drag_begin (GtkWidget *widget,
1743 GdkDragContext *context,
1746 GossipContactListPriv *priv;
1747 GtkTreeSelection *selection;
1748 GtkTreeModel *model;
1752 priv = GET_PRIV (widget);
1754 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1755 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1759 path = gtk_tree_model_get_path (model, &iter);
1760 priv->drag_row = gtk_tree_row_reference_new (model, path);
1761 gtk_tree_path_free (path);
1765 contact_list_drag_data_get (GtkWidget *widget,
1766 GdkDragContext *context,
1767 GtkSelectionData *selection,
1772 GossipContactListPriv *priv;
1773 GtkTreePath *src_path;
1775 GtkTreeModel *model;
1776 GossipContact *contact;
1779 priv = GET_PRIV (widget);
1781 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1782 if (!priv->drag_row) {
1786 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1791 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1792 gtk_tree_path_free (src_path);
1796 gtk_tree_path_free (src_path);
1798 contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1803 id = gossip_contact_get_id (contact);
1804 g_object_unref (contact);
1807 case DND_DRAG_TYPE_CONTACT_ID:
1808 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1809 (guchar*)id, strlen (id) + 1);
1818 contact_list_drag_end (GtkWidget *widget,
1819 GdkDragContext *context,
1822 GossipContactListPriv *priv;
1824 priv = GET_PRIV (widget);
1826 if (priv->drag_row) {
1827 gtk_tree_row_reference_free (priv->drag_row);
1828 priv->drag_row = NULL;
1833 contact_list_cell_set_background (GossipContactList *list,
1834 GtkCellRenderer *cell,
1841 g_return_if_fail (list != NULL);
1842 g_return_if_fail (cell != NULL);
1844 style = gtk_widget_get_style (GTK_WIDGET (list));
1848 color = style->bg[GTK_STATE_SELECTED];
1850 /* Here we take the current theme colour and add it to
1851 * the colour for white and average the two. This
1852 * gives a colour which is inline with the theme but
1855 color.red = (color.red + (style->white).red) / 2;
1856 color.green = (color.green + (style->white).green) / 2;
1857 color.blue = (color.blue + (style->white).blue) / 2;
1860 "cell-background-gdk", &color,
1864 "cell-background-gdk", NULL,
1869 gint color_sum_normal;
1870 gint color_sum_selected;
1872 color = style->base[GTK_STATE_SELECTED];
1873 color_sum_normal = color.red+color.green+color.blue;
1874 color = style->base[GTK_STATE_NORMAL];
1875 color_sum_selected = color.red+color.green+color.blue;
1876 color = style->text_aa[GTK_STATE_INSENSITIVE];
1878 if (color_sum_normal < color_sum_selected) {
1879 /* Found a light theme */
1880 color.red = (color.red + (style->white).red) / 2;
1881 color.green = (color.green + (style->white).green) / 2;
1882 color.blue = (color.blue + (style->white).blue) / 2;
1884 /* Found a dark theme */
1885 color.red = (color.red + (style->black).red) / 2;
1886 color.green = (color.green + (style->black).green) / 2;
1887 color.blue = (color.blue + (style->black).blue) / 2;
1891 "cell-background-gdk", &color,
1898 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1899 GtkCellRenderer *cell,
1900 GtkTreeModel *model,
1902 GossipContactList *list)
1908 gtk_tree_model_get (model, iter,
1909 COL_IS_GROUP, &is_group,
1910 COL_IS_ACTIVE, &is_active,
1911 COL_ICON_STATUS, &icon_name,
1915 "visible", !is_group,
1916 "icon-name", icon_name,
1921 contact_list_cell_set_background (list, cell, is_group, is_active);
1925 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1926 GtkCellRenderer *cell,
1927 GtkTreeModel *model,
1929 GossipContactList *list)
1932 gboolean show_avatar;
1936 gtk_tree_model_get (model, iter,
1937 COL_PIXBUF_AVATAR, &pixbuf,
1938 COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1939 COL_IS_GROUP, &is_group,
1940 COL_IS_ACTIVE, &is_active,
1944 "visible", !is_group && show_avatar,
1949 g_object_unref (pixbuf);
1952 contact_list_cell_set_background (list, cell, is_group, is_active);
1956 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1957 GtkCellRenderer *cell,
1958 GtkTreeModel *model,
1960 GossipContactList *list)
1964 gboolean show_status;
1966 gtk_tree_model_get (model, iter,
1967 COL_IS_GROUP, &is_group,
1968 COL_IS_ACTIVE, &is_active,
1969 COL_STATUS_VISIBLE, &show_status,
1973 "show-status", show_status,
1976 contact_list_cell_set_background (list, cell, is_group, is_active);
1980 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1981 GtkCellRenderer *cell,
1982 GtkTreeModel *model,
1984 GossipContactList *list)
1989 gtk_tree_model_get (model, iter,
1990 COL_IS_GROUP, &is_group,
1991 COL_IS_ACTIVE, &is_active,
1994 if (gtk_tree_model_iter_has_child (model, iter)) {
1996 gboolean row_expanded;
1998 path = gtk_tree_model_get_path (model, iter);
1999 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
2000 gtk_tree_path_free (path);
2004 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
2007 g_object_set (cell, "visible", FALSE, NULL);
2010 contact_list_cell_set_background (list, cell, is_group, is_active);
2014 contact_list_get_contact_menu (GossipContactList *list,
2015 gboolean can_send_file,
2016 gboolean can_show_log)
2018 GossipContactListPriv *priv;
2022 priv = GET_PRIV (list);
2024 /* Sort out sensitive items */
2025 action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
2026 gtk_action_set_sensitive (action, can_show_log);
2028 action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
2029 gtk_action_set_visible (action, can_send_file);
2031 widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
2037 gossip_contact_list_get_group_menu (GossipContactList *list)
2039 GossipContactListPriv *priv;
2042 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2044 priv = GET_PRIV (list);
2046 widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
2052 gossip_contact_list_get_contact_menu (GossipContactList *list,
2053 GossipContact *contact)
2056 gboolean can_show_log;
2057 gboolean can_send_file;
2059 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2060 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
2062 can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
2063 can_send_file = FALSE;
2065 menu = contact_list_get_contact_menu (list,
2072 contact_list_button_press_event_cb (GossipContactList *list,
2073 GdkEventButton *event,
2076 GossipContactListPriv *priv;
2077 GossipContact *contact;
2079 GtkTreeSelection *selection;
2080 GtkTreeModel *model;
2082 gboolean row_exists;
2085 if (event->button != 3) {
2089 priv = GET_PRIV (list);
2091 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2092 model = GTK_TREE_MODEL (priv->store);
2094 gtk_widget_grab_focus (GTK_WIDGET (list));
2096 row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
2104 gtk_tree_selection_unselect_all (selection);
2105 gtk_tree_selection_select_path (selection, path);
2107 gtk_tree_model_get_iter (model, &iter, path);
2108 gtk_tree_path_free (path);
2110 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2113 menu = gossip_contact_list_get_contact_menu (list, contact);
2114 g_object_unref (contact);
2116 menu = gossip_contact_list_get_group_menu (list);
2123 gtk_widget_show (menu);
2125 gtk_menu_popup (GTK_MENU (menu),
2126 NULL, NULL, NULL, NULL,
2127 event->button, event->time);
2133 contact_list_row_activated_cb (GossipContactList *list,
2135 GtkTreeViewColumn *col,
2138 GossipContact *contact;
2140 GtkTreeModel *model;
2143 view = GTK_TREE_VIEW (list);
2144 model = gtk_tree_view_get_model (view);
2146 gtk_tree_model_get_iter (model, &iter, path);
2147 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2150 contact_list_action_activated (list, contact);
2151 g_object_unref (contact);
2156 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2161 GtkTreeModel *model;
2165 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2167 gtk_tree_model_get (model, iter,
2171 expanded = GPOINTER_TO_INT (user_data);
2172 gossip_contact_group_set_expanded (name, expanded);
2178 contact_list_state_sort_func (GtkTreeModel *model,
2179 GtkTreeIter *iter_a,
2180 GtkTreeIter *iter_b,
2184 gchar *name_a, *name_b;
2185 gboolean is_separator_a, is_separator_b;
2186 GossipContact *contact_a, *contact_b;
2187 GossipPresence *presence_a, *presence_b;
2188 McPresence state_a, state_b;
2190 gtk_tree_model_get (model, iter_a,
2192 COL_CONTACT, &contact_a,
2193 COL_IS_SEPARATOR, &is_separator_a,
2195 gtk_tree_model_get (model, iter_b,
2197 COL_CONTACT, &contact_b,
2198 COL_IS_SEPARATOR, &is_separator_b,
2201 /* Separator or group? */
2202 if (is_separator_a || is_separator_b) {
2203 if (is_separator_a) {
2205 } else if (is_separator_b) {
2208 } else if (!contact_a && contact_b) {
2210 } else if (contact_a && !contact_b) {
2212 } else if (!contact_a && !contact_b) {
2214 ret_val = g_utf8_collate (name_a, name_b);
2221 /* If we managed to get this far, we can start looking at
2224 presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
2225 presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
2227 if (!presence_a && presence_b) {
2229 } else if (presence_a && !presence_b) {
2231 } else if (!presence_a && !presence_b) {
2232 /* Both offline, sort by name */
2233 ret_val = g_utf8_collate (name_a, name_b);
2235 state_a = gossip_presence_get_state (presence_a);
2236 state_b = gossip_presence_get_state (presence_b);
2238 if (state_a < state_b) {
2240 } else if (state_a > state_b) {
2243 /* Fallback: compare by name */
2244 ret_val = g_utf8_collate (name_a, name_b);
2253 g_object_unref (contact_a);
2257 g_object_unref (contact_b);
2264 contact_list_name_sort_func (GtkTreeModel *model,
2265 GtkTreeIter *iter_a,
2266 GtkTreeIter *iter_b,
2269 gchar *name_a, *name_b;
2270 GossipContact *contact_a, *contact_b;
2271 gboolean is_separator_a, is_separator_b;
2274 gtk_tree_model_get (model, iter_a,
2276 COL_CONTACT, &contact_a,
2277 COL_IS_SEPARATOR, &is_separator_a,
2279 gtk_tree_model_get (model, iter_b,
2281 COL_CONTACT, &contact_b,
2282 COL_IS_SEPARATOR, &is_separator_b,
2285 /* If contact is NULL it means it's a group. */
2287 if (is_separator_a || is_separator_b) {
2288 if (is_separator_a) {
2290 } else if (is_separator_b) {
2293 } else if (!contact_a && contact_b) {
2295 } else if (contact_a && !contact_b) {
2298 ret_val = g_utf8_collate (name_a, name_b);
2305 g_object_unref (contact_a);
2309 g_object_unref (contact_b);
2316 contact_list_filter_show_contact (GossipContact *contact,
2317 const gchar *filter)
2322 /* Check contact id */
2323 str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
2324 visible = G_STR_EMPTY (str) || strstr (str, filter);
2331 /* Check contact name */
2332 str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
2333 visible = G_STR_EMPTY (str) || strstr (str, filter);
2340 contact_list_filter_show_group (GossipContactList *list,
2342 const gchar *filter)
2344 GossipContactListPriv *priv;
2345 GList *contacts, *l;
2347 gboolean show_group = FALSE;
2349 priv = GET_PRIV (list);
2351 str = g_utf8_casefold (group, -1);
2356 /* If the filter is the partially the group name, we show the
2359 if (strstr (str, filter)) {
2364 /* At this point, we need to check in advance if this
2365 * group should be shown because a contact we want to
2366 * show exists in it.
2368 contacts = empathy_contact_manager_get_contacts (priv->manager);
2369 for (l = contacts; l && !show_group; l = l->next) {
2370 if (!gossip_contact_is_in_group (l->data, group)) {
2374 if (contact_list_filter_show_contact (l->data, filter)) {
2378 g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2379 g_list_free (contacts);
2386 contact_list_filter_func (GtkTreeModel *model,
2388 GossipContactList *list)
2390 GossipContactListPriv *priv;
2392 gboolean is_separator;
2393 gboolean visible = TRUE;
2395 priv = GET_PRIV (list);
2397 if (G_STR_EMPTY (priv->filter_text)) {
2401 /* Check to see if iter matches any group names */
2402 gtk_tree_model_get (model, iter,
2403 COL_IS_GROUP, &is_group,
2404 COL_IS_SEPARATOR, &is_separator,
2410 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
2411 visible &= contact_list_filter_show_group (list,
2415 } else if (is_separator) {
2416 /* Do nothing here */
2418 GossipContact *contact;
2420 /* Check contact id */
2421 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
2422 visible &= contact_list_filter_show_contact (contact,
2424 g_object_unref (contact);
2431 contact_list_iter_equal_contact (GtkTreeModel *model,
2433 GossipContact *contact)
2438 gtk_tree_model_get (model, iter,
2446 equal = (c == contact);
2453 contact_list_find_contact_foreach (GtkTreeModel *model,
2458 if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2460 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2463 /* We want to find ALL contacts that match, this means if we
2464 * have the same contact in 3 groups, all iters should be
2471 contact_list_find_contact (GossipContactList *list,
2472 GossipContact *contact)
2474 GossipContactListPriv *priv;
2475 GtkTreeModel *model;
2479 priv = GET_PRIV (list);
2481 memset (&fc, 0, sizeof (fc));
2483 fc.contact = contact;
2485 model = GTK_TREE_MODEL (priv->store);
2486 gtk_tree_model_foreach (model,
2487 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2498 contact_list_action_cb (GtkAction *action,
2499 GossipContactList *list)
2501 GossipContact *contact;
2505 name = gtk_action_get_name (action);
2510 gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2512 contact = gossip_contact_list_get_selected (list);
2513 group = gossip_contact_list_get_selected_group (list);
2515 if (contact && strcmp (name, "Chat") == 0) {
2516 contact_list_action_activated (list, contact);
2518 else if (contact && strcmp (name, "Information") == 0) {
2520 else if (contact && strcmp (name, "Edit") == 0) {
2522 else if (contact && strcmp (name, "Remove") == 0) {
2524 else if (contact && strcmp (name, "Invite") == 0) {
2526 else if (contact && strcmp (name, "SendFile") == 0) {
2528 else if (contact && strcmp (name, "Log") == 0) {
2530 else if (group && strcmp (name, "Rename") == 0) {
2535 g_object_unref (contact);
2540 contact_list_action_activated (GossipContactList *list,
2541 GossipContact *contact)
2545 mc = gossip_mission_control_new ();
2546 mission_control_request_channel (mc,
2547 gossip_contact_get_account (contact),
2548 TP_IFACE_CHANNEL_TYPE_TEXT,
2549 gossip_contact_get_handle (contact),
2550 TP_HANDLE_TYPE_CONTACT,
2552 g_object_unref (mc);
2556 contact_list_update_list_mode_foreach (GtkTreeModel *model,
2559 GossipContactList *list)
2561 GossipContactListPriv *priv;
2562 gboolean show_avatar = FALSE;
2564 priv = GET_PRIV (list);
2566 if (priv->show_avatars && !priv->is_compact) {
2570 gtk_tree_store_set (priv->store, iter,
2571 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2572 COL_STATUS_VISIBLE, !priv->is_compact,
2579 gossip_contact_list_new (void)
2581 return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2585 gossip_contact_list_get_selected (GossipContactList *list)
2587 GossipContactListPriv *priv;
2588 GtkTreeSelection *selection;
2590 GtkTreeModel *model;
2591 GossipContact *contact;
2593 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2595 priv = GET_PRIV (list);
2597 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2598 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2602 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2608 gossip_contact_list_get_selected_group (GossipContactList *list)
2610 GossipContactListPriv *priv;
2611 GtkTreeSelection *selection;
2613 GtkTreeModel *model;
2617 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2619 priv = GET_PRIV (list);
2621 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2622 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2626 gtk_tree_model_get (model, &iter,
2627 COL_IS_GROUP, &is_group,
2640 gossip_contact_list_get_show_offline (GossipContactList *list)
2642 GossipContactListPriv *priv;
2644 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2646 priv = GET_PRIV (list);
2648 return priv->show_offline;
2652 gossip_contact_list_get_show_avatars (GossipContactList *list)
2654 GossipContactListPriv *priv;
2656 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2658 priv = GET_PRIV (list);
2660 return priv->show_avatars;
2664 gossip_contact_list_get_is_compact (GossipContactList *list)
2666 GossipContactListPriv *priv;
2668 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2670 priv = GET_PRIV (list);
2672 return priv->is_compact;
2675 GossipContactListSort
2676 gossip_contact_list_get_sort_criterium (GossipContactList *list)
2678 GossipContactListPriv *priv;
2680 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), 0);
2682 priv = GET_PRIV (list);
2684 return priv->sort_criterium;
2688 gossip_contact_list_set_show_offline (GossipContactList *list,
2689 gboolean show_offline)
2691 GossipContactListPriv *priv;
2692 GList *contacts, *l;
2693 gboolean show_active;
2695 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2697 priv = GET_PRIV (list);
2699 priv->show_offline = show_offline;
2700 show_active = priv->show_active;
2702 /* Disable temporarily. */
2703 priv->show_active = FALSE;
2705 contacts = empathy_contact_manager_get_contacts (priv->manager);
2706 for (l = contacts; l; l = l->next) {
2707 GossipContact *contact;
2709 contact = GOSSIP_CONTACT (l->data);
2711 contact_list_contact_update (list, contact);
2713 g_object_unref (contact);
2715 g_list_free (contacts);
2717 /* Restore to original setting. */
2718 priv->show_active = show_active;
2722 gossip_contact_list_set_show_avatars (GossipContactList *list,
2723 gboolean show_avatars)
2725 GossipContactListPriv *priv;
2726 GtkTreeModel *model;
2728 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2730 priv = GET_PRIV (list);
2732 priv->show_avatars = show_avatars;
2734 model = GTK_TREE_MODEL (priv->store);
2736 gtk_tree_model_foreach (model,
2737 (GtkTreeModelForeachFunc)
2738 contact_list_update_list_mode_foreach,
2743 gossip_contact_list_set_is_compact (GossipContactList *list,
2744 gboolean is_compact)
2746 GossipContactListPriv *priv;
2747 GtkTreeModel *model;
2749 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2751 priv = GET_PRIV (list);
2753 priv->is_compact = is_compact;
2755 model = GTK_TREE_MODEL (priv->store);
2757 gtk_tree_model_foreach (model,
2758 (GtkTreeModelForeachFunc)
2759 contact_list_update_list_mode_foreach,
2764 gossip_contact_list_set_sort_criterium (GossipContactList *list,
2765 GossipContactListSort sort_criterium)
2767 GossipContactListPriv *priv;
2769 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2771 priv = GET_PRIV (list);
2773 priv->sort_criterium = sort_criterium;
2775 switch (sort_criterium) {
2776 case GOSSIP_CONTACT_LIST_SORT_STATE:
2777 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2779 GTK_SORT_ASCENDING);
2782 case GOSSIP_CONTACT_LIST_SORT_NAME:
2783 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2785 GTK_SORT_ASCENDING);
2791 gossip_contact_list_set_filter (GossipContactList *list,
2792 const gchar *filter)
2794 GossipContactListPriv *priv;
2796 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2798 priv = GET_PRIV (list);
2800 g_free (priv->filter_text);
2802 priv->filter_text = g_utf8_casefold (filter, -1);
2804 priv->filter_text = NULL;
2807 gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
2808 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));