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 <libtelepathy/tp-helpers.h>
34 #include <libmissioncontrol/mc-account.h>
35 #include <libmissioncontrol/mission-control.h>
37 #include <libempathy/empathy-contact-manager.h>
38 #include <libempathy/gossip-debug.h>
39 #include <libempathy/empathy-session.h>
41 #include "gossip-contact-list.h"
42 #include "gossip-contact-groups.h"
43 #include "gossip-cell-renderer-expander.h"
44 #include "gossip-cell-renderer-text.h"
45 #include "gossip-stock.h"
46 #include "gossip-ui-utils.h"
47 //#include "gossip-chat-invite.h"
48 //#include "gossip-contact-info-dialog.h"
49 //#include "gossip-edit-contact-dialog.h"
50 //#include "gossip-ft-window.h"
51 //#include "gossip-log-window.h"
53 #define DEBUG_DOMAIN "ContactListUI"
55 /* Flashing delay for icons (milliseconds). */
56 #define FLASH_TIMEOUT 500
58 /* Active users are those which have recently changed state
59 * (e.g. online, offline or from normal to a busy state).
62 /* Time user is shown as active */
63 #define ACTIVE_USER_SHOW_TIME 7000
65 /* Time after connecting which we wait before active users are enabled */
66 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
68 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
70 struct _GossipContactListPriv {
71 EmpathyContactManager *manager;
76 GtkTreeRowReference *drag_row;
82 gboolean show_offline;
83 gboolean show_avatars;
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_sort_func (GtkTreeModel *model,
242 static gboolean contact_list_filter_func (GtkTreeModel *model,
244 GossipContactList *list);
245 static GList * contact_list_find_contact (GossipContactList *list,
246 GossipContact *contact);
247 static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
251 static void contact_list_action_cb (GtkAction *action,
252 GossipContactList *list);
253 static void contact_list_action_activated (GossipContactList *list,
254 GossipContact *contact);
255 static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
258 GossipContactList *list);
263 COL_PIXBUF_AVATAR_VISIBLE,
283 static const GtkActionEntry entries[] = {
284 { "ContactMenu", NULL,
285 N_("_Contact"), NULL, NULL,
289 N_("_Group"),NULL, NULL,
292 { "Chat", GOSSIP_STOCK_MESSAGE,
293 N_("_Chat"), NULL, N_("Chat with contact"),
294 G_CALLBACK (contact_list_action_cb)
296 { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
297 N_("Infor_mation"), "<control>I", N_("View contact information"),
298 G_CALLBACK (contact_list_action_cb)
301 N_("Re_name"), NULL, N_("Rename"),
302 G_CALLBACK (contact_list_action_cb)
304 { "Edit", GTK_STOCK_EDIT,
305 N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
306 G_CALLBACK (contact_list_action_cb)
308 { "Remove", GTK_STOCK_REMOVE,
309 N_("_Remove"), NULL, N_("Remove contact"),
310 G_CALLBACK (contact_list_action_cb)
312 { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
313 N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
314 G_CALLBACK (contact_list_action_cb)
317 N_("_Send File..."), NULL, N_("Send a file"),
318 G_CALLBACK (contact_list_action_cb)
320 { "Log", GTK_STOCK_JUSTIFY_LEFT,
321 N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
322 G_CALLBACK (contact_list_action_cb)
326 static guint n_entries = G_N_ELEMENTS (entries);
328 static const gchar *ui_info =
330 " <popup name='Contact'>"
331 " <menuitem action='Chat'/>"
332 " <menuitem action='Log'/>"
333 " <menuitem action='SendFile'/>"
335 " <menuitem action='Invite'/>"
337 " <menuitem action='Edit'/>"
338 " <menuitem action='Remove'/>"
340 " <menuitem action='Information'/>"
342 " <popup name='Group'>"
343 " <menuitem action='Rename'/>"
348 DND_DRAG_TYPE_CONTACT_ID,
350 DND_DRAG_TYPE_STRING,
353 static const GtkTargetEntry drag_types_dest[] = {
354 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
355 { "text/uri-list", 0, DND_DRAG_TYPE_URL },
356 { "text/plain", 0, DND_DRAG_TYPE_STRING },
357 { "STRING", 0, DND_DRAG_TYPE_STRING },
360 static const GtkTargetEntry drag_types_source[] = {
361 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
364 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
365 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
367 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
370 gossip_contact_list_class_init (GossipContactListClass *klass)
372 GObjectClass *object_class = G_OBJECT_CLASS (klass);
374 object_class->finalize = contact_list_finalize;
375 object_class->get_property = contact_list_get_property;
376 object_class->set_property = contact_list_set_property;
378 g_object_class_install_property (object_class,
380 g_param_spec_boolean ("show-offline",
382 "Whether contact list should display "
386 g_object_class_install_property (object_class,
388 g_param_spec_boolean ("show-avatars",
390 "Whether contact list should display "
391 "avatars for contacts",
394 g_object_class_install_property (object_class,
396 g_param_spec_boolean ("is-compact",
398 "Whether the contact list is in compact mode or not",
402 g_object_class_install_property (object_class,
404 g_param_spec_string ("filter",
406 "The text to use to filter the contact list",
410 g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
414 gossip_contact_list_init (GossipContactList *list)
416 GossipContactListPriv *priv;
417 GtkActionGroup *action_group;
419 GError *error = NULL;
421 priv = GET_PRIV (list);
423 priv->manager = empathy_session_get_contact_manager ();
424 g_object_ref (priv->manager);
425 priv->is_compact = FALSE;
426 priv->show_active = TRUE;
427 priv->show_avatars = TRUE;
429 contact_list_create_model (list);
430 contact_list_setup_view (list);
431 empathy_contact_manager_setup (priv->manager);
433 /* Get saved group states. */
434 gossip_contact_groups_get_all ();
436 /* Set up UI Manager */
437 priv->ui = gtk_ui_manager_new ();
439 action_group = gtk_action_group_new ("Actions");
440 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
441 gtk_action_group_add_actions (action_group, entries, n_entries, list);
442 gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
444 if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
445 g_warning ("Could not build contact menus from string:'%s'", error->message);
446 g_error_free (error);
449 g_object_unref (action_group);
451 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list),
452 contact_list_row_separator_func,
455 /* Signal connection. */
456 g_signal_connect (priv->manager,
458 G_CALLBACK (contact_list_contact_added_cb),
460 g_signal_connect (priv->manager,
462 G_CALLBACK (contact_list_contact_removed_cb),
465 /* Connect to tree view signals rather than override. */
466 g_signal_connect (list,
467 "button-press-event",
468 G_CALLBACK (contact_list_button_press_event_cb),
470 g_signal_connect (list,
472 G_CALLBACK (contact_list_row_activated_cb),
474 g_signal_connect (list,
476 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
477 GINT_TO_POINTER (TRUE));
478 g_signal_connect (list,
480 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
481 GINT_TO_POINTER (FALSE));
483 /* Add contacts already created */
484 contacts = empathy_contact_manager_get_contacts (priv->manager);
485 for (l = contacts; l; l = l->next) {
486 GossipContact *contact;
490 contact_list_contact_added_cb (priv->manager, contact, list);
492 g_object_unref (contact);
494 g_list_free (contacts);
498 contact_list_finalize (GObject *object)
500 GossipContactListPriv *priv;
502 priv = GET_PRIV (object);
504 /* FIXME: disconnect all signals on the manager and contacts */
506 g_object_unref (priv->manager);
507 g_object_unref (priv->ui);
508 g_object_unref (priv->store);
509 g_object_unref (priv->filter);
510 g_free (priv->filter_text);
512 G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
516 contact_list_get_property (GObject *object,
521 GossipContactListPriv *priv;
523 priv = GET_PRIV (object);
526 case PROP_SHOW_OFFLINE:
527 g_value_set_boolean (value, priv->show_offline);
529 case PROP_SHOW_AVATARS:
530 g_value_set_boolean (value, priv->show_avatars);
532 case PROP_IS_COMPACT:
533 g_value_set_boolean (value, priv->is_compact);
536 g_value_set_string (value, priv->filter_text);
539 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
545 contact_list_set_property (GObject *object,
550 GossipContactListPriv *priv;
552 priv = GET_PRIV (object);
555 case PROP_SHOW_OFFLINE:
556 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
557 g_value_get_boolean (value));
559 case PROP_SHOW_AVATARS:
560 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
561 g_value_get_boolean (value));
563 case PROP_IS_COMPACT:
564 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
565 g_value_get_boolean (value));
568 gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
569 g_value_get_string (value));
572 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
578 contact_list_row_separator_func (GtkTreeModel *model,
582 gboolean is_separator = FALSE;
584 gtk_tree_model_get (model, iter,
585 COL_IS_SEPARATOR, &is_separator,
592 contact_list_contact_update (GossipContactList *list,
593 GossipContact *contact)
595 GossipContactListPriv *priv;
596 ShowActiveData *data;
600 gboolean should_be_in_list;
601 gboolean was_online = TRUE;
602 gboolean now_online = FALSE;
603 gboolean set_model = FALSE;
604 gboolean do_remove = FALSE;
605 gboolean do_set_active = FALSE;
606 gboolean do_set_refresh = FALSE;
607 GdkPixbuf *pixbuf_presence;
608 GdkPixbuf *pixbuf_avatar;
610 priv = GET_PRIV (list);
612 model = GTK_TREE_MODEL (priv->store);
614 iters = contact_list_find_contact (list, contact);
621 /* Get online state now. */
622 now_online = gossip_contact_is_online (contact);
624 if (priv->show_offline || now_online) {
625 should_be_in_list = TRUE;
627 should_be_in_list = FALSE;
630 if (!in_list && !should_be_in_list) {
632 gossip_debug (DEBUG_DOMAIN,
633 "Contact:'%s' in list:NO, should be:NO",
634 gossip_contact_get_name (contact));
636 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
640 else if (in_list && !should_be_in_list) {
641 gossip_debug (DEBUG_DOMAIN,
642 "Contact:'%s' in list:YES, should be:NO",
643 gossip_contact_get_name (contact));
645 if (priv->show_active) {
647 do_set_active = TRUE;
648 do_set_refresh = TRUE;
651 gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
653 gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
654 contact_list_remove_contact (list, contact);
657 else if (!in_list && should_be_in_list) {
658 gossip_debug (DEBUG_DOMAIN,
659 "Contact:'%s' in list:NO, should be:YES",
660 gossip_contact_get_name (contact));
662 contact_list_add_contact (list, contact);
664 if (priv->show_active) {
665 do_set_active = TRUE;
667 gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
670 gossip_debug (DEBUG_DOMAIN,
671 "Contact:'%s' in list:YES, should be:YES",
672 gossip_contact_get_name (contact));
674 /* Get online state before. */
675 if (iters && g_list_length (iters) > 0) {
676 gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
679 /* Is this really an update or an online/offline. */
680 if (priv->show_active) {
681 if (was_online != now_online) {
684 do_set_active = TRUE;
685 do_set_refresh = TRUE;
688 str = "online -> offline";
690 str = "offline -> online";
693 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
695 /* Was TRUE for presence updates. */
696 /* do_set_active = FALSE; */
697 do_set_refresh = TRUE;
699 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
706 pixbuf_presence = gossip_pixbuf_for_contact (contact);
707 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
708 for (l = iters; l && set_model; l = l->next) {
709 gtk_tree_store_set (priv->store, l->data,
710 COL_PIXBUF_STATUS, pixbuf_presence,
711 COL_STATUS, gossip_contact_get_status (contact),
712 COL_IS_ONLINE, now_online,
713 COL_NAME, gossip_contact_get_name (contact),
714 COL_PIXBUF_AVATAR, pixbuf_avatar,
718 if (pixbuf_presence) {
719 g_object_unref (pixbuf_presence);
722 g_object_unref (pixbuf_avatar);
725 if (priv->show_active && do_set_active) {
726 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
729 data = contact_list_contact_active_new (list, contact, do_remove);
730 g_timeout_add (ACTIVE_USER_SHOW_TIME,
731 (GSourceFunc) contact_list_contact_active_cb,
736 /* FIXME: when someone goes online then offline quickly, the
737 * first timeout sets the user to be inactive and the second
738 * timeout removes the user from the contact list, really we
739 * should remove the first timeout.
741 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
746 contact_list_contact_added_cb (EmpathyContactManager *manager,
747 GossipContact *contact,
748 GossipContactList *list)
750 GossipContactListPriv *priv;
752 priv = GET_PRIV (list);
754 gossip_debug (DEBUG_DOMAIN,
755 "Contact:'%s' added",
756 gossip_contact_get_name (contact));
758 g_signal_connect (contact, "notify::groups",
759 G_CALLBACK (contact_list_contact_groups_updated_cb),
761 g_signal_connect (contact, "notify::presence",
762 G_CALLBACK (contact_list_contact_updated_cb),
764 g_signal_connect (contact, "notify::name",
765 G_CALLBACK (contact_list_contact_updated_cb),
767 g_signal_connect (contact, "notify::avatar",
768 G_CALLBACK (contact_list_contact_updated_cb),
770 g_signal_connect (contact, "notify::type",
771 G_CALLBACK (contact_list_contact_updated_cb),
774 contact_list_add_contact (list, contact);
778 contact_list_contact_groups_updated_cb (GossipContact *contact,
780 GossipContactList *list)
782 GossipContactListPriv *priv;
784 priv = GET_PRIV (list);
786 if (priv->show_offline || gossip_contact_is_online (contact)) {
790 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
791 gossip_contact_get_name (contact));
793 /* We do this to make sure the groups are correct, if not, we
794 * would have to check the groups already set up for each
795 * contact and then see what has been updated.
797 contact_list_remove_contact (list, contact);
798 contact_list_add_contact (list, contact);
802 contact_list_contact_updated_cb (GossipContact *contact,
804 GossipContactList *list)
806 gossip_debug (DEBUG_DOMAIN,
807 "Contact:'%s' updated, checking roster is in sync...",
808 gossip_contact_get_name (contact));
810 contact_list_contact_update (list, contact);
814 contact_list_contact_removed_cb (EmpathyContactManager *manager,
815 GossipContact *contact,
816 GossipContactList *list)
818 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
819 gossip_contact_get_name (contact));
821 /* Disconnect signals */
822 g_signal_handlers_disconnect_by_func (contact,
823 G_CALLBACK (contact_list_contact_groups_updated_cb),
825 g_signal_handlers_disconnect_by_func (contact,
826 G_CALLBACK (contact_list_contact_updated_cb),
829 contact_list_remove_contact (list, contact);
833 contact_list_contact_set_active (GossipContactList *list,
834 GossipContact *contact,
836 gboolean set_changed)
838 GossipContactListPriv *priv;
842 priv = GET_PRIV (list);
844 model = GTK_TREE_MODEL (priv->store);
846 iters = contact_list_find_contact (list, contact);
847 for (l = iters; l; l = l->next) {
850 gtk_tree_store_set (priv->store, l->data,
851 COL_IS_ACTIVE, active,
854 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
857 path = gtk_tree_model_get_path (model, l->data);
858 gtk_tree_model_row_changed (model, path, l->data);
859 gtk_tree_path_free (path);
863 g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
868 static ShowActiveData *
869 contact_list_contact_active_new (GossipContactList *list,
870 GossipContact *contact,
873 ShowActiveData *data;
875 g_return_val_if_fail (list != NULL, NULL);
876 g_return_val_if_fail (contact != NULL, NULL);
878 gossip_debug (DEBUG_DOMAIN,
879 "Contact:'%s' now active, and %s be removed",
880 gossip_contact_get_name (contact),
881 remove ? "WILL" : "WILL NOT");
883 data = g_slice_new0 (ShowActiveData);
885 data->list = g_object_ref (list);
886 data->contact = g_object_ref (contact);
888 data->remove = remove;
894 contact_list_contact_active_free (ShowActiveData *data)
896 g_return_if_fail (data != NULL);
898 g_object_unref (data->contact);
899 g_object_unref (data->list);
901 g_slice_free (ShowActiveData, data);
905 contact_list_contact_active_cb (ShowActiveData *data)
907 GossipContactListPriv *priv;
909 g_return_val_if_fail (data != NULL, FALSE);
911 priv = GET_PRIV (data->list);
914 !priv->show_offline &&
915 !gossip_contact_is_online (data->contact)) {
916 gossip_debug (DEBUG_DOMAIN,
917 "Contact:'%s' active timeout, removing item",
918 gossip_contact_get_name (data->contact));
919 contact_list_remove_contact (data->list,
923 gossip_debug (DEBUG_DOMAIN,
924 "Contact:'%s' no longer active",
925 gossip_contact_get_name (data->contact));
926 contact_list_contact_set_active (data->list,
931 contact_list_contact_active_free (data);
937 contact_list_get_parent_group (GtkTreeModel *model,
939 gboolean *path_is_group)
941 GtkTreeIter parent_iter, iter;
945 g_return_val_if_fail (model != NULL, NULL);
946 g_return_val_if_fail (path != NULL, NULL);
947 g_return_val_if_fail (path_is_group != NULL, NULL);
949 if (!gtk_tree_model_get_iter (model, &iter, path)) {
953 gtk_tree_model_get (model, &iter,
954 COL_IS_GROUP, &is_group,
958 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
964 gtk_tree_model_get (model, &iter,
965 COL_IS_GROUP, &is_group,
972 *path_is_group = TRUE;
975 gtk_tree_model_get (model, &iter,
983 contact_list_get_group_foreach (GtkTreeModel *model,
991 /* Groups are only at the top level. */
992 if (gtk_tree_path_get_depth (path) != 1) {
996 gtk_tree_model_get (model, iter,
998 COL_IS_GROUP, &is_group,
1001 if (is_group && strcmp (str, fg->name) == 0) {
1012 contact_list_get_group (GossipContactList *list,
1014 GtkTreeIter *iter_group_to_set,
1015 GtkTreeIter *iter_separator_to_set,
1018 GossipContactListPriv *priv;
1019 GtkTreeModel *model;
1020 GtkTreeIter iter_group, iter_separator;
1023 priv = GET_PRIV (list);
1025 memset (&fg, 0, sizeof (fg));
1029 model = GTK_TREE_MODEL (priv->store);
1030 gtk_tree_model_foreach (model,
1031 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1039 gtk_tree_store_append (priv->store, &iter_group, NULL);
1040 gtk_tree_store_set (priv->store, &iter_group,
1041 COL_PIXBUF_STATUS, NULL,
1044 COL_IS_ACTIVE, FALSE,
1045 COL_IS_SEPARATOR, FALSE,
1048 if (iter_group_to_set) {
1049 *iter_group_to_set = iter_group;
1052 gtk_tree_store_append (priv->store,
1055 gtk_tree_store_set (priv->store, &iter_separator,
1056 COL_IS_SEPARATOR, TRUE,
1059 if (iter_separator_to_set) {
1060 *iter_separator_to_set = iter_separator;
1067 if (iter_group_to_set) {
1068 *iter_group_to_set = fg.iter;
1071 iter_separator = fg.iter;
1073 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1074 gboolean is_separator;
1076 gtk_tree_model_get (model, &iter_separator,
1077 COL_IS_SEPARATOR, &is_separator,
1080 if (is_separator && iter_separator_to_set) {
1081 *iter_separator_to_set = iter_separator;
1088 contact_list_add_contact (GossipContactList *list,
1089 GossipContact *contact)
1091 GossipContactListPriv *priv;
1092 GtkTreeIter iter, iter_group, iter_separator;
1093 GtkTreeModel *model;
1096 priv = GET_PRIV (list);
1098 if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1102 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1104 /* If no groups just add it at the top level. */
1105 groups = gossip_contact_get_groups (contact);
1107 GdkPixbuf *pixbuf_status;
1108 GdkPixbuf *pixbuf_avatar;
1109 gboolean show_avatar = FALSE;
1111 pixbuf_status = gossip_pixbuf_for_contact (contact);
1112 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1114 if (priv->show_avatars && !priv->is_compact) {
1118 gossip_debug (DEBUG_DOMAIN, "");
1119 gossip_debug (DEBUG_DOMAIN,
1120 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1122 gossip_debug (DEBUG_DOMAIN,
1123 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
1125 G_IS_OBJECT (contact) ? "yes" : "no",
1126 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1128 gtk_tree_store_append (priv->store, &iter, NULL);
1129 gtk_tree_store_set (priv->store, &iter,
1130 COL_PIXBUF_STATUS, pixbuf_status,
1131 COL_PIXBUF_AVATAR, pixbuf_avatar,
1132 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1133 COL_NAME, gossip_contact_get_name (contact),
1134 COL_STATUS, gossip_contact_get_status (contact),
1135 COL_STATUS_VISIBLE, !priv->is_compact,
1136 COL_CONTACT, contact,
1137 COL_IS_GROUP, FALSE,
1138 COL_IS_ACTIVE, FALSE,
1139 COL_IS_ONLINE, gossip_contact_is_online (contact),
1140 COL_IS_SEPARATOR, FALSE,
1143 gossip_debug (DEBUG_DOMAIN,
1144 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1145 gossip_debug (DEBUG_DOMAIN, "");
1147 if (pixbuf_avatar) {
1148 g_object_unref (pixbuf_avatar);
1150 if (pixbuf_status) {
1151 g_object_unref (pixbuf_status);
1155 /* Else add to each group. */
1156 for (l = groups; l; l = l->next) {
1158 GtkTreeIter model_iter_group;
1159 GdkPixbuf *pixbuf_status;
1160 GdkPixbuf *pixbuf_avatar;
1164 gboolean show_avatar = FALSE;
1171 pixbuf_status = gossip_pixbuf_for_contact (contact);
1172 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1174 contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
1176 if (priv->show_avatars && !priv->is_compact) {
1180 gossip_debug (DEBUG_DOMAIN, "");
1181 gossip_debug (DEBUG_DOMAIN,
1182 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1184 gossip_debug (DEBUG_DOMAIN,
1185 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
1187 G_IS_OBJECT (contact) ? "yes" : "no",
1188 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1190 gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
1191 gtk_tree_store_set (priv->store, &iter,
1192 COL_PIXBUF_STATUS, pixbuf_status,
1193 COL_PIXBUF_AVATAR, pixbuf_avatar,
1194 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1195 COL_NAME, gossip_contact_get_name (contact),
1196 COL_STATUS, gossip_contact_get_status (contact),
1197 COL_STATUS_VISIBLE, !priv->is_compact,
1198 COL_CONTACT, contact,
1199 COL_IS_GROUP, FALSE,
1200 COL_IS_ACTIVE, FALSE,
1201 COL_IS_ONLINE, gossip_contact_is_online (contact),
1202 COL_IS_SEPARATOR, FALSE,
1205 gossip_debug (DEBUG_DOMAIN,
1206 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1207 gossip_debug (DEBUG_DOMAIN, "");
1209 if (pixbuf_avatar) {
1210 g_object_unref (pixbuf_avatar);
1212 if (pixbuf_status) {
1213 g_object_unref (pixbuf_status);
1220 found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),
1227 path = gtk_tree_model_get_path (model, &model_iter_group);
1232 if (gossip_contact_group_get_expanded (name)) {
1233 g_signal_handlers_block_by_func (list,
1234 contact_list_row_expand_or_collapse_cb,
1235 GINT_TO_POINTER (TRUE));
1236 gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1237 g_signal_handlers_unblock_by_func (list,
1238 contact_list_row_expand_or_collapse_cb,
1239 GINT_TO_POINTER (TRUE));
1241 g_signal_handlers_block_by_func (list,
1242 contact_list_row_expand_or_collapse_cb,
1243 GINT_TO_POINTER (FALSE));
1244 gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1245 g_signal_handlers_unblock_by_func (list,
1246 contact_list_row_expand_or_collapse_cb,
1247 GINT_TO_POINTER (FALSE));
1250 gtk_tree_path_free (path);
1255 contact_list_remove_contact (GossipContactList *list,
1256 GossipContact *contact)
1258 GossipContactListPriv *priv;
1259 GtkTreeModel *model;
1262 priv = GET_PRIV (list);
1264 iters = contact_list_find_contact (list, contact);
1269 /* Clean up model */
1270 model = GTK_TREE_MODEL (priv->store);
1272 for (l = iters; l; l = l->next) {
1275 /* NOTE: it is only <= 2 here because we have
1276 * separators after the group name, otherwise it
1279 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1280 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1281 gtk_tree_store_remove (priv->store, &parent);
1283 gtk_tree_store_remove (priv->store, l->data);
1287 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1288 g_list_free (iters);
1292 contact_list_create_model (GossipContactList *list)
1294 GossipContactListPriv *priv;
1295 GtkTreeModel *model;
1297 priv = GET_PRIV (list);
1300 g_object_unref (priv->store);
1304 g_object_unref (priv->filter);
1307 priv->store = gtk_tree_store_new (COL_COUNT,
1308 GDK_TYPE_PIXBUF, /* Status pixbuf */
1309 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
1310 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
1311 G_TYPE_STRING, /* Name */
1312 G_TYPE_STRING, /* Status string */
1313 G_TYPE_BOOLEAN, /* Show status */
1314 GOSSIP_TYPE_CONTACT, /* Contact type */
1315 G_TYPE_BOOLEAN, /* Is group */
1316 G_TYPE_BOOLEAN, /* Is active */
1317 G_TYPE_BOOLEAN, /* Is online */
1318 G_TYPE_BOOLEAN); /* Is separator */
1320 /* Save normal model */
1321 model = GTK_TREE_MODEL (priv->store);
1323 /* Set up sorting */
1324 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1326 contact_list_sort_func,
1329 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
1331 GTK_SORT_ASCENDING);
1334 priv->filter = gtk_tree_model_filter_new (model, NULL);
1336 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
1337 (GtkTreeModelFilterVisibleFunc)
1338 contact_list_filter_func,
1341 gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
1345 contact_list_search_equal_func (GtkTreeModel *model,
1349 gpointer search_data)
1351 gchar *name, *name_folded;
1359 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1365 name_folded = g_utf8_casefold (name, -1);
1366 key_folded = g_utf8_casefold (key, -1);
1368 if (name_folded && key_folded &&
1369 strstr (name_folded, key_folded)) {
1376 g_free (name_folded);
1377 g_free (key_folded);
1383 contact_list_setup_view (GossipContactList *list)
1385 GtkCellRenderer *cell;
1386 GtkTreeViewColumn *col;
1389 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1390 contact_list_search_equal_func,
1395 "headers-visible", FALSE,
1396 "reorderable", TRUE,
1397 "show-expanders", FALSE,
1400 col = gtk_tree_view_column_new ();
1403 cell = gtk_cell_renderer_pixbuf_new ();
1404 gtk_tree_view_column_pack_start (col, cell, FALSE);
1405 gtk_tree_view_column_set_cell_data_func (
1407 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1417 cell = gossip_cell_renderer_text_new ();
1418 gtk_tree_view_column_pack_start (col, cell, TRUE);
1419 gtk_tree_view_column_set_cell_data_func (
1421 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1424 gtk_tree_view_column_add_attribute (col, cell,
1426 gtk_tree_view_column_add_attribute (col, cell,
1427 "status", COL_STATUS);
1428 gtk_tree_view_column_add_attribute (col, cell,
1429 "is_group", COL_IS_GROUP);
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_avatar_cell_data_func,
1448 cell = gossip_cell_renderer_expander_new ();
1449 gtk_tree_view_column_pack_end (col, cell, FALSE);
1450 gtk_tree_view_column_set_cell_data_func (
1452 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1455 /* Actually add the column now we have added all cell renderers */
1456 gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1459 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1460 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1464 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1465 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1469 /* Note: We support the COPY action too, but need to make the
1470 * MOVE action the default.
1472 gtk_drag_source_set (GTK_WIDGET (list),
1475 G_N_ELEMENTS (drag_types_source),
1478 gtk_drag_dest_set (GTK_WIDGET (list),
1479 GTK_DEST_DEFAULT_ALL,
1481 G_N_ELEMENTS (drag_types_dest),
1482 GDK_ACTION_MOVE | GDK_ACTION_LINK);
1484 g_signal_connect (GTK_WIDGET (list),
1485 "drag-data-received",
1486 G_CALLBACK (contact_list_drag_data_received),
1489 /* FIXME: noticed but when you drag the row over the treeview
1490 * fast, it seems to stop redrawing itself, if we don't
1491 * connect this signal, all is fine.
1493 g_signal_connect (GTK_WIDGET (list),
1495 G_CALLBACK (contact_list_drag_motion),
1498 g_signal_connect (GTK_WIDGET (list),
1500 G_CALLBACK (contact_list_drag_begin),
1502 g_signal_connect (GTK_WIDGET (list),
1504 G_CALLBACK (contact_list_drag_data_get),
1506 g_signal_connect (GTK_WIDGET (list),
1508 G_CALLBACK (contact_list_drag_end),
1513 contact_list_drag_data_received (GtkWidget *widget,
1514 GdkDragContext *context,
1517 GtkSelectionData *selection,
1522 GossipContactListPriv *priv;
1523 GtkTreeModel *model;
1525 GtkTreeViewDropPosition position;
1526 GossipContact *contact;
1531 gboolean drag_success = TRUE;
1532 gboolean drag_del = FALSE;
1534 priv = GET_PRIV (widget);
1536 id = (const gchar*) selection->data;
1537 gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1538 context->action == GDK_ACTION_MOVE ? "move" : "",
1539 context->action == GDK_ACTION_COPY ? "copy" : "",
1542 /* FIXME: This is ambigous, an id can come from multiple accounts */
1543 contact = empathy_contact_manager_find (priv->manager, id);
1545 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1549 groups = gossip_contact_get_groups (contact);
1551 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1558 if (g_list_length (groups) != 1) {
1559 /* if they have dragged a contact out of a
1560 * group then we would set the contact to have
1561 * NO groups but only if they were ONE group
1562 * to begin with - should we do this
1563 * regardless to how many groups they are in
1564 * already or not at all?
1569 gossip_contact_set_groups (contact, NULL);
1571 GList *l, *new_groups;
1575 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1576 name = contact_list_get_parent_group (model, path, &is_group);
1578 if (groups && name &&
1579 g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1584 /* Get source group information. */
1585 priv = GET_PRIV (widget);
1586 if (!priv->drag_row) {
1591 path = gtk_tree_row_reference_get_path (priv->drag_row);
1597 old_group = contact_list_get_parent_group (model, path, &is_group);
1598 gtk_tree_path_free (path);
1600 if (!name && old_group && GDK_ACTION_MOVE) {
1601 drag_success = FALSE;
1604 if (context->action == GDK_ACTION_MOVE) {
1608 /* Create new groups GList. */
1609 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1613 if (context->action == GDK_ACTION_MOVE &&
1614 old_group != NULL &&
1615 strcmp (str, old_group) == 0) {
1623 new_groups = g_list_append (new_groups, g_strdup (str));
1628 new_groups = g_list_append (new_groups, name);
1630 gossip_contact_set_groups (contact, new_groups);
1636 gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1640 contact_list_drag_motion (GtkWidget *widget,
1641 GdkDragContext *context,
1647 static DragMotionData *dm = NULL;
1650 gboolean is_different = FALSE;
1651 gboolean cleanup = TRUE;
1653 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1664 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1665 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1670 if (!is_different && !cleanup) {
1675 gtk_tree_path_free (dm->path);
1676 if (dm->timeout_id) {
1677 g_source_remove (dm->timeout_id);
1685 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1686 dm = g_new0 (DragMotionData, 1);
1688 dm->list = GOSSIP_CONTACT_LIST (widget);
1689 dm->path = gtk_tree_path_copy (path);
1691 dm->timeout_id = g_timeout_add (
1693 (GSourceFunc) contact_list_drag_motion_cb,
1701 contact_list_drag_motion_cb (DragMotionData *data)
1703 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1707 data->timeout_id = 0;
1713 contact_list_drag_begin (GtkWidget *widget,
1714 GdkDragContext *context,
1717 GossipContactListPriv *priv;
1718 GtkTreeSelection *selection;
1719 GtkTreeModel *model;
1723 priv = GET_PRIV (widget);
1725 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1726 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1730 path = gtk_tree_model_get_path (model, &iter);
1731 priv->drag_row = gtk_tree_row_reference_new (model, path);
1732 gtk_tree_path_free (path);
1736 contact_list_drag_data_get (GtkWidget *widget,
1737 GdkDragContext *context,
1738 GtkSelectionData *selection,
1743 GossipContactListPriv *priv;
1744 GtkTreePath *src_path;
1746 GtkTreeModel *model;
1747 GossipContact *contact;
1750 priv = GET_PRIV (widget);
1752 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1753 if (!priv->drag_row) {
1757 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1762 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1763 gtk_tree_path_free (src_path);
1767 gtk_tree_path_free (src_path);
1769 contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1774 id = gossip_contact_get_id (contact);
1775 g_object_unref (contact);
1778 case DND_DRAG_TYPE_CONTACT_ID:
1779 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1780 (guchar*)id, strlen (id) + 1);
1789 contact_list_drag_end (GtkWidget *widget,
1790 GdkDragContext *context,
1793 GossipContactListPriv *priv;
1795 priv = GET_PRIV (widget);
1797 if (priv->drag_row) {
1798 gtk_tree_row_reference_free (priv->drag_row);
1799 priv->drag_row = NULL;
1804 contact_list_cell_set_background (GossipContactList *list,
1805 GtkCellRenderer *cell,
1812 g_return_if_fail (list != NULL);
1813 g_return_if_fail (cell != NULL);
1815 style = gtk_widget_get_style (GTK_WIDGET (list));
1819 color = style->bg[GTK_STATE_SELECTED];
1821 /* Here we take the current theme colour and add it to
1822 * the colour for white and average the two. This
1823 * gives a colour which is inline with the theme but
1826 color.red = (color.red + (style->white).red) / 2;
1827 color.green = (color.green + (style->white).green) / 2;
1828 color.blue = (color.blue + (style->white).blue) / 2;
1831 "cell-background-gdk", &color,
1835 "cell-background-gdk", NULL,
1840 "cell-background-gdk", NULL,
1843 gint color_sum_normal;
1844 gint color_sum_selected;
1846 color = style->base[GTK_STATE_SELECTED];
1847 color_sum_normal = color.red+color.green+color.blue;
1848 color = style->base[GTK_STATE_NORMAL];
1849 color_sum_selected = color.red+color.green+color.blue;
1850 color = style->text_aa[GTK_STATE_INSENSITIVE];
1852 if (color_sum_normal < color_sum_selected) {
1853 /* Found a light theme */
1854 color.red = (color.red + (style->white).red) / 2;
1855 color.green = (color.green + (style->white).green) / 2;
1856 color.blue = (color.blue + (style->white).blue) / 2;
1858 /* Found a dark theme */
1859 color.red = (color.red + (style->black).red) / 2;
1860 color.green = (color.green + (style->black).green) / 2;
1861 color.blue = (color.blue + (style->black).blue) / 2;
1865 "cell-background-gdk", &color,
1872 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1873 GtkCellRenderer *cell,
1874 GtkTreeModel *model,
1876 GossipContactList *list)
1882 gtk_tree_model_get (model, iter,
1883 COL_IS_GROUP, &is_group,
1884 COL_IS_ACTIVE, &is_active,
1885 COL_PIXBUF_STATUS, &pixbuf,
1889 "visible", !is_group,
1894 g_object_unref (pixbuf);
1897 contact_list_cell_set_background (list, cell, is_group, is_active);
1901 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1902 GtkCellRenderer *cell,
1903 GtkTreeModel *model,
1905 GossipContactList *list)
1908 gboolean show_avatar;
1912 gtk_tree_model_get (model, iter,
1913 COL_PIXBUF_AVATAR, &pixbuf,
1914 COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1915 COL_IS_GROUP, &is_group,
1916 COL_IS_ACTIVE, &is_active,
1920 "visible", !is_group && show_avatar,
1925 g_object_unref (pixbuf);
1928 contact_list_cell_set_background (list, cell, is_group, is_active);
1932 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1933 GtkCellRenderer *cell,
1934 GtkTreeModel *model,
1936 GossipContactList *list)
1940 gboolean show_status;
1942 gtk_tree_model_get (model, iter,
1943 COL_IS_GROUP, &is_group,
1944 COL_IS_ACTIVE, &is_active,
1945 COL_STATUS_VISIBLE, &show_status,
1949 "show-status", show_status,
1952 contact_list_cell_set_background (list, cell, is_group, is_active);
1956 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1957 GtkCellRenderer *cell,
1958 GtkTreeModel *model,
1960 GossipContactList *list)
1965 gtk_tree_model_get (model, iter,
1966 COL_IS_GROUP, &is_group,
1967 COL_IS_ACTIVE, &is_active,
1970 if (gtk_tree_model_iter_has_child (model, iter)) {
1972 gboolean row_expanded;
1974 path = gtk_tree_model_get_path (model, iter);
1975 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
1976 gtk_tree_path_free (path);
1980 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1983 g_object_set (cell, "visible", FALSE, NULL);
1986 contact_list_cell_set_background (list, cell, is_group, is_active);
1990 contact_list_get_contact_menu (GossipContactList *list,
1991 gboolean can_send_file,
1992 gboolean can_show_log)
1994 GossipContactListPriv *priv;
1998 priv = GET_PRIV (list);
2000 /* Sort out sensitive items */
2001 action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
2002 gtk_action_set_sensitive (action, can_show_log);
2004 action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
2005 gtk_action_set_visible (action, can_send_file);
2007 widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
2013 gossip_contact_list_get_group_menu (GossipContactList *list)
2015 GossipContactListPriv *priv;
2018 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2020 priv = GET_PRIV (list);
2022 widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
2028 gossip_contact_list_get_contact_menu (GossipContactList *list,
2029 GossipContact *contact)
2032 gboolean can_show_log;
2033 gboolean can_send_file;
2035 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2036 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
2038 can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
2039 can_send_file = FALSE;
2041 menu = contact_list_get_contact_menu (list,
2048 contact_list_button_press_event_cb (GossipContactList *list,
2049 GdkEventButton *event,
2052 GossipContactListPriv *priv;
2053 GossipContact *contact;
2055 GtkTreeSelection *selection;
2056 GtkTreeModel *model;
2058 gboolean row_exists;
2061 if (event->button != 3) {
2065 priv = GET_PRIV (list);
2067 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2068 model = GTK_TREE_MODEL (priv->store);
2070 gtk_widget_grab_focus (GTK_WIDGET (list));
2072 row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
2080 gtk_tree_selection_unselect_all (selection);
2081 gtk_tree_selection_select_path (selection, path);
2083 gtk_tree_model_get_iter (model, &iter, path);
2084 gtk_tree_path_free (path);
2086 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2089 menu = gossip_contact_list_get_contact_menu (list, contact);
2090 g_object_unref (contact);
2092 menu = gossip_contact_list_get_group_menu (list);
2099 gtk_widget_show (menu);
2101 gtk_menu_popup (GTK_MENU (menu),
2102 NULL, NULL, NULL, NULL,
2103 event->button, event->time);
2109 contact_list_row_activated_cb (GossipContactList *list,
2111 GtkTreeViewColumn *col,
2114 GossipContact *contact;
2116 GtkTreeModel *model;
2119 view = GTK_TREE_VIEW (list);
2120 model = gtk_tree_view_get_model (view);
2122 gtk_tree_model_get_iter (model, &iter, path);
2123 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2126 contact_list_action_activated (list, contact);
2127 g_object_unref (contact);
2132 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2137 GtkTreeModel *model;
2141 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2143 gtk_tree_model_get (model, iter,
2147 expanded = GPOINTER_TO_INT (user_data);
2148 gossip_contact_group_set_expanded (name, expanded);
2154 contact_list_sort_func (GtkTreeModel *model,
2155 GtkTreeIter *iter_a,
2156 GtkTreeIter *iter_b,
2159 gchar *name_a, *name_b;
2160 GossipContact *contact_a, *contact_b;
2161 gboolean is_separator_a, is_separator_b;
2164 gtk_tree_model_get (model, iter_a,
2166 COL_CONTACT, &contact_a,
2167 COL_IS_SEPARATOR, &is_separator_a,
2169 gtk_tree_model_get (model, iter_b,
2171 COL_CONTACT, &contact_b,
2172 COL_IS_SEPARATOR, &is_separator_b,
2175 /* If contact is NULL it means it's a group. */
2177 if (is_separator_a || is_separator_b) {
2178 if (is_separator_a) {
2180 } else if (is_separator_b) {
2183 } else if (!contact_a && contact_b) {
2185 } else if (contact_a && !contact_b) {
2188 ret_val = g_utf8_collate (name_a, name_b);
2195 g_object_unref (contact_a);
2199 g_object_unref (contact_b);
2206 contact_list_filter_show_contact (GossipContact *contact,
2207 const gchar *filter)
2212 /* Check contact id */
2213 str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
2214 visible = G_STR_EMPTY (str) || strstr (str, filter);
2221 /* Check contact name */
2222 str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
2223 visible = G_STR_EMPTY (str) || strstr (str, filter);
2230 contact_list_filter_show_group (GossipContactList *list,
2232 const gchar *filter)
2234 GossipContactListPriv *priv;
2235 GList *contacts, *l;
2237 gboolean show_group = FALSE;
2239 priv = GET_PRIV (list);
2241 str = g_utf8_casefold (group, -1);
2246 /* If the filter is the partially the group name, we show the
2249 if (strstr (str, filter)) {
2254 /* At this point, we need to check in advance if this
2255 * group should be shown because a contact we want to
2256 * show exists in it.
2258 contacts = empathy_contact_manager_get_contacts (priv->manager);
2259 for (l = contacts; l && !show_group; l = l->next) {
2260 if (!gossip_contact_is_in_group (l->data, group)) {
2264 if (contact_list_filter_show_contact (l->data, filter)) {
2268 g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2269 g_list_free (contacts);
2276 contact_list_filter_func (GtkTreeModel *model,
2278 GossipContactList *list)
2280 GossipContactListPriv *priv;
2282 gboolean is_separator;
2283 gboolean visible = TRUE;
2285 priv = GET_PRIV (list);
2287 if (G_STR_EMPTY (priv->filter_text)) {
2291 /* Check to see if iter matches any group names */
2292 gtk_tree_model_get (model, iter,
2293 COL_IS_GROUP, &is_group,
2294 COL_IS_SEPARATOR, &is_separator,
2300 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
2301 visible &= contact_list_filter_show_group (list,
2305 } else if (is_separator) {
2306 /* Do nothing here */
2308 GossipContact *contact;
2310 /* Check contact id */
2311 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
2312 visible &= contact_list_filter_show_contact (contact,
2314 g_object_unref (contact);
2321 contact_list_iter_equal_contact (GtkTreeModel *model,
2323 GossipContact *contact)
2328 gtk_tree_model_get (model, iter,
2336 equal = (c == contact);
2343 contact_list_find_contact_foreach (GtkTreeModel *model,
2348 if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2350 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2353 /* We want to find ALL contacts that match, this means if we
2354 * have the same contact in 3 groups, all iters should be
2361 contact_list_find_contact (GossipContactList *list,
2362 GossipContact *contact)
2364 GossipContactListPriv *priv;
2365 GtkTreeModel *model;
2369 priv = GET_PRIV (list);
2371 memset (&fc, 0, sizeof (fc));
2373 fc.contact = contact;
2375 model = GTK_TREE_MODEL (priv->store);
2376 gtk_tree_model_foreach (model,
2377 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2388 contact_list_action_cb (GtkAction *action,
2389 GossipContactList *list)
2391 GossipContact *contact;
2395 name = gtk_action_get_name (action);
2400 gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2402 contact = gossip_contact_list_get_selected (list);
2403 group = gossip_contact_list_get_selected_group (list);
2405 if (contact && strcmp (name, "Chat") == 0) {
2406 contact_list_action_activated (list, contact);
2408 else if (contact && strcmp (name, "Information") == 0) {
2410 else if (contact && strcmp (name, "Edit") == 0) {
2412 else if (contact && strcmp (name, "Remove") == 0) {
2414 else if (contact && strcmp (name, "Invite") == 0) {
2416 else if (contact && strcmp (name, "SendFile") == 0) {
2418 else if (contact && strcmp (name, "Log") == 0) {
2420 else if (group && strcmp (name, "Rename") == 0) {
2425 g_object_unref (contact);
2430 contact_list_action_activated (GossipContactList *list,
2431 GossipContact *contact)
2435 mc = mission_control_new (tp_get_bus ());
2436 mission_control_request_channel (mc,
2437 gossip_contact_get_account (contact),
2438 TP_IFACE_CHANNEL_TYPE_TEXT,
2439 gossip_contact_get_handle (contact),
2440 TP_HANDLE_TYPE_CONTACT,
2442 g_object_unref (mc);
2446 contact_list_update_list_mode_foreach (GtkTreeModel *model,
2449 GossipContactList *list)
2451 GossipContactListPriv *priv;
2452 gboolean show_avatar = FALSE;
2454 priv = GET_PRIV (list);
2456 if (priv->show_avatars && !priv->is_compact) {
2460 gtk_tree_store_set (priv->store, iter,
2461 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2462 COL_STATUS_VISIBLE, !priv->is_compact,
2469 gossip_contact_list_new (void)
2471 return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2475 gossip_contact_list_get_selected (GossipContactList *list)
2477 GossipContactListPriv *priv;
2478 GtkTreeSelection *selection;
2480 GtkTreeModel *model;
2481 GossipContact *contact;
2483 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2485 priv = GET_PRIV (list);
2487 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2488 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2492 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2498 gossip_contact_list_get_selected_group (GossipContactList *list)
2500 GossipContactListPriv *priv;
2501 GtkTreeSelection *selection;
2503 GtkTreeModel *model;
2507 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2509 priv = GET_PRIV (list);
2511 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2512 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2516 gtk_tree_model_get (model, &iter,
2517 COL_IS_GROUP, &is_group,
2530 gossip_contact_list_get_show_offline (GossipContactList *list)
2532 GossipContactListPriv *priv;
2534 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2536 priv = GET_PRIV (list);
2538 return priv->show_offline;
2542 gossip_contact_list_get_show_avatars (GossipContactList *list)
2544 GossipContactListPriv *priv;
2546 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2548 priv = GET_PRIV (list);
2550 return priv->show_avatars;
2554 gossip_contact_list_get_is_compact (GossipContactList *list)
2556 GossipContactListPriv *priv;
2558 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2560 priv = GET_PRIV (list);
2562 return priv->is_compact;
2566 gossip_contact_list_set_show_offline (GossipContactList *list,
2567 gboolean show_offline)
2569 GossipContactListPriv *priv;
2570 GList *contacts, *l;
2571 gboolean show_active;
2573 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2575 priv = GET_PRIV (list);
2577 priv->show_offline = show_offline;
2578 show_active = priv->show_active;
2580 /* Disable temporarily. */
2581 priv->show_active = FALSE;
2583 contacts = empathy_contact_manager_get_contacts (priv->manager);
2584 for (l = contacts; l; l = l->next) {
2585 GossipContact *contact;
2587 contact = GOSSIP_CONTACT (l->data);
2589 contact_list_contact_update (list, contact);
2591 g_object_unref (contact);
2593 g_list_free (contacts);
2595 /* Restore to original setting. */
2596 priv->show_active = show_active;
2600 gossip_contact_list_set_show_avatars (GossipContactList *list,
2601 gboolean show_avatars)
2603 GossipContactListPriv *priv;
2604 GtkTreeModel *model;
2606 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2608 priv = GET_PRIV (list);
2610 priv->show_avatars = show_avatars;
2612 model = GTK_TREE_MODEL (priv->store);
2614 gtk_tree_model_foreach (model,
2615 (GtkTreeModelForeachFunc)
2616 contact_list_update_list_mode_foreach,
2621 gossip_contact_list_set_is_compact (GossipContactList *list,
2622 gboolean is_compact)
2624 GossipContactListPriv *priv;
2625 GtkTreeModel *model;
2627 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2629 priv = GET_PRIV (list);
2631 priv->is_compact = is_compact;
2633 model = GTK_TREE_MODEL (priv->store);
2635 gtk_tree_model_foreach (model,
2636 (GtkTreeModelForeachFunc)
2637 contact_list_update_list_mode_foreach,
2642 gossip_contact_list_set_filter (GossipContactList *list,
2643 const gchar *filter)
2645 GossipContactListPriv *priv;
2647 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2649 priv = GET_PRIV (list);
2651 g_free (priv->filter_text);
2653 priv->filter_text = g_utf8_casefold (filter, -1);
2655 priv->filter_text = NULL;
2658 gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
2659 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));