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>
34 #include <libempathy/empathy-contact-manager.h>
35 #include <libempathy/gossip-debug.h>
36 #include <libempathy/empathy-session.h>
38 #include "gossip-contact-list.h"
39 #include "gossip-contact-groups.h"
40 #include "gossip-cell-renderer-expander.h"
41 #include "gossip-cell-renderer-text.h"
42 #include "gossip-stock.h"
43 #include "gossip-ui-utils.h"
44 //#include "gossip-chat-invite.h"
45 //#include "gossip-contact-info-dialog.h"
46 //#include "gossip-edit-contact-dialog.h"
47 //#include "gossip-ft-window.h"
48 //#include "gossip-log-window.h"
50 #define DEBUG_DOMAIN "ContactListUI"
52 /* Flashing delay for icons (milliseconds). */
53 #define FLASH_TIMEOUT 500
55 /* Active users are those which have recently changed state
56 * (e.g. online, offline or from normal to a busy state).
59 /* Time user is shown as active */
60 #define ACTIVE_USER_SHOW_TIME 7000
62 /* Time after connecting which we wait before active users are enabled */
63 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
65 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
67 struct _GossipContactListPriv {
68 EmpathyContactManager *manager;
73 GtkTreeRowReference *drag_row;
75 gboolean show_offline;
76 gboolean show_avatars;
88 GossipContact *contact;
94 GossipContactList *list;
100 GossipContactList *list;
101 GossipContact *contact;
105 static void gossip_contact_list_class_init (GossipContactListClass *klass);
106 static void gossip_contact_list_init (GossipContactList *list);
107 static void contact_list_finalize (GObject *object);
108 static void contact_list_get_property (GObject *object,
112 static void contact_list_set_property (GObject *object,
116 static void contact_list_contact_update (GossipContactList *list,
117 GossipContact *contact);
118 static void contact_list_contact_added_cb (EmpathyContactManager *manager,
119 GossipContact *contact,
120 GossipContactList *list);
121 static void contact_list_contact_updated_cb (GossipContact *contact,
123 GossipContactList *list);
124 static void contact_list_contact_groups_updated_cb (GossipContact *contact,
126 GossipContactList *list);
127 static void contact_list_contact_removed_cb (EmpathyContactManager *manager,
128 GossipContact *contact,
129 GossipContactList *list);
130 static void contact_list_contact_set_active (GossipContactList *list,
131 GossipContact *contact,
133 gboolean set_changed);
134 static ShowActiveData *
135 contact_list_contact_active_new (GossipContactList *list,
136 GossipContact *contact,
138 static void contact_list_contact_active_free (ShowActiveData *data);
139 static gboolean contact_list_contact_active_cb (ShowActiveData *data);
140 static gchar * contact_list_get_parent_group (GtkTreeModel *model,
142 gboolean *path_is_group);
143 static void contact_list_get_group (GossipContactList *list,
145 GtkTreeIter *iter_to_set,
147 static gboolean contact_list_get_group_foreach (GtkTreeModel *model,
151 static void contact_list_add_contact (GossipContactList *list,
152 GossipContact *contact);
153 static void contact_list_remove_contact (GossipContactList *list,
154 GossipContact *contact);
155 static void contact_list_create_model (GossipContactList *list);
156 static gboolean contact_list_search_equal_func (GtkTreeModel *model,
160 gpointer search_data);
161 static void contact_list_setup_view (GossipContactList *list);
162 static void contact_list_drag_data_received (GtkWidget *widget,
163 GdkDragContext *context,
166 GtkSelectionData *selection,
170 static gboolean contact_list_drag_motion (GtkWidget *widget,
171 GdkDragContext *context,
176 static gboolean contact_list_drag_motion_cb (DragMotionData *data);
177 static void contact_list_drag_begin (GtkWidget *widget,
178 GdkDragContext *context,
180 static void contact_list_drag_data_get (GtkWidget *widget,
181 GdkDragContext *contact,
182 GtkSelectionData *selection,
186 static void contact_list_drag_end (GtkWidget *widget,
187 GdkDragContext *context,
189 static void contact_list_cell_set_background (GossipContactList *list,
190 GtkCellRenderer *cell,
193 static void contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
194 GtkCellRenderer *cell,
197 GossipContactList *list);
198 static void contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
199 GtkCellRenderer *cell,
202 GossipContactList *list);
203 static void contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
204 GtkCellRenderer *cell,
207 GossipContactList *list);
208 static void contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
209 GtkCellRenderer *cell,
212 GossipContactList *list);
213 static GtkWidget *contact_list_get_contact_menu (GossipContactList *list,
214 gboolean can_send_file,
215 gboolean can_show_log);
216 static gboolean contact_list_button_press_event_cb (GossipContactList *list,
217 GdkEventButton *event,
219 static void contact_list_row_activated_cb (GossipContactList *list,
221 GtkTreeViewColumn *col,
223 static void contact_list_row_expand_or_collapse_cb (GossipContactList *list,
227 static gint contact_list_sort_func (GtkTreeModel *model,
231 static GList * contact_list_find_contact (GossipContactList *list,
232 GossipContact *contact);
233 static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
237 static void contact_list_action_cb (GtkAction *action,
238 GossipContactList *list);
239 static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
242 GossipContactList *list);
255 static guint signals[LAST_SIGNAL];
260 COL_PIXBUF_AVATAR_VISIBLE,
278 static const GtkActionEntry entries[] = {
279 { "ContactMenu", NULL,
280 N_("_Contact"), NULL, NULL,
284 N_("_Group"),NULL, NULL,
287 { "Chat", GOSSIP_STOCK_MESSAGE,
288 N_("_Chat"), NULL, N_("Chat with contact"),
289 G_CALLBACK (contact_list_action_cb)
291 { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
292 N_("Infor_mation"), "<control>I", N_("View contact information"),
293 G_CALLBACK (contact_list_action_cb)
296 N_("Re_name"), NULL, N_("Rename"),
297 G_CALLBACK (contact_list_action_cb)
299 { "Edit", GTK_STOCK_EDIT,
300 N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
301 G_CALLBACK (contact_list_action_cb)
303 { "Remove", GTK_STOCK_REMOVE,
304 N_("_Remove"), NULL, N_("Remove contact"),
305 G_CALLBACK (contact_list_action_cb)
307 { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
308 N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
309 G_CALLBACK (contact_list_action_cb)
312 N_("_Send File..."), NULL, N_("Send a file"),
313 G_CALLBACK (contact_list_action_cb)
315 { "Log", GTK_STOCK_JUSTIFY_LEFT,
316 N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
317 G_CALLBACK (contact_list_action_cb)
321 static guint n_entries = G_N_ELEMENTS (entries);
323 static const gchar *ui_info =
325 " <popup name='Contact'>"
326 " <menuitem action='Chat'/>"
327 " <menuitem action='Log'/>"
328 " <menuitem action='SendFile'/>"
330 " <menuitem action='Invite'/>"
332 " <menuitem action='Edit'/>"
333 " <menuitem action='Remove'/>"
335 " <menuitem action='Information'/>"
337 " <popup name='Group'>"
338 " <menuitem action='Rename'/>"
343 DND_DRAG_TYPE_CONTACT_ID,
345 DND_DRAG_TYPE_STRING,
348 static const GtkTargetEntry drag_types_dest[] = {
349 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
350 { "text/uri-list", 0, DND_DRAG_TYPE_URL },
351 { "text/plain", 0, DND_DRAG_TYPE_STRING },
352 { "STRING", 0, DND_DRAG_TYPE_STRING },
355 static const GtkTargetEntry drag_types_source[] = {
356 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
359 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
360 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
362 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
365 gossip_contact_list_class_init (GossipContactListClass *klass)
367 GObjectClass *object_class = G_OBJECT_CLASS (klass);
369 object_class->finalize = contact_list_finalize;
370 object_class->get_property = contact_list_get_property;
371 object_class->set_property = contact_list_set_property;
373 signals[CONTACT_CHAT] =
374 g_signal_new ("contact-chat",
375 G_TYPE_FROM_CLASS (klass),
379 g_cclosure_marshal_VOID__OBJECT,
381 1, GOSSIP_TYPE_CONTACT);
382 signals[CONTACT_INFORMATION] =
383 g_signal_new ("contact-information",
384 G_TYPE_FROM_CLASS (klass),
388 g_cclosure_marshal_VOID__OBJECT,
390 1, GOSSIP_TYPE_CONTACT);
391 signals[CONTACT_EDIT] =
392 g_signal_new ("contact-edit",
393 G_TYPE_FROM_CLASS (klass),
397 g_cclosure_marshal_VOID__OBJECT,
399 1, GOSSIP_TYPE_CONTACT);
400 signals[CONTACT_REMOVE] =
401 g_signal_new ("contact-remove",
402 G_TYPE_FROM_CLASS (klass),
406 g_cclosure_marshal_VOID__OBJECT,
408 1, GOSSIP_TYPE_CONTACT);
409 signals[CONTACT_INVITE] =
410 g_signal_new ("contact-invite",
411 G_TYPE_FROM_CLASS (klass),
415 g_cclosure_marshal_VOID__OBJECT,
417 1, GOSSIP_TYPE_CONTACT);
418 signals[CONTACT_SEND_FILE] =
419 g_signal_new ("contact-send-file",
420 G_TYPE_FROM_CLASS (klass),
424 g_cclosure_marshal_VOID__OBJECT,
426 1, GOSSIP_TYPE_CONTACT);
427 signals[CONTACT_LOG] =
428 g_signal_new ("contact-log",
429 G_TYPE_FROM_CLASS (klass),
433 g_cclosure_marshal_VOID__OBJECT,
435 1, GOSSIP_TYPE_CONTACT);
436 signals[GROUP_RENAME] =
437 g_signal_new ("group-rename",
438 G_TYPE_FROM_CLASS (klass),
442 g_cclosure_marshal_VOID__STRING,
447 g_object_class_install_property (object_class,
449 g_param_spec_boolean ("show-offline",
451 "Whether contact list should display "
455 g_object_class_install_property (object_class,
457 g_param_spec_boolean ("show-avatars",
459 "Whether contact list should display "
460 "avatars for contacts",
463 g_object_class_install_property (object_class,
465 g_param_spec_boolean ("is-compact",
467 "Whether the contact list is in compact mode or not",
471 g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
475 gossip_contact_list_init (GossipContactList *list)
477 GossipContactListPriv *priv;
478 GtkActionGroup *action_group;
480 GError *error = NULL;
482 priv = GET_PRIV (list);
484 priv->manager = empathy_session_get_contact_manager ();
485 g_object_ref (priv->manager);
486 priv->is_compact = FALSE;
487 priv->show_active = TRUE;
488 priv->show_avatars = TRUE;
490 contact_list_create_model (list);
491 contact_list_setup_view (list);
492 empathy_contact_manager_setup (priv->manager);
494 /* Get saved group states. */
495 gossip_contact_groups_get_all ();
497 /* Set up UI Manager */
498 priv->ui = gtk_ui_manager_new ();
500 action_group = gtk_action_group_new ("Actions");
501 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
502 gtk_action_group_add_actions (action_group, entries, n_entries, list);
503 gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
505 if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
506 g_warning ("Could not build contact menus from string:'%s'", error->message);
507 g_error_free (error);
510 g_object_unref (action_group);
512 /* Signal connection. */
513 g_signal_connect (priv->manager,
515 G_CALLBACK (contact_list_contact_added_cb),
517 g_signal_connect (priv->manager,
519 G_CALLBACK (contact_list_contact_removed_cb),
522 /* Connect to tree view signals rather than override. */
523 g_signal_connect (list,
524 "button-press-event",
525 G_CALLBACK (contact_list_button_press_event_cb),
527 g_signal_connect (list,
529 G_CALLBACK (contact_list_row_activated_cb),
531 g_signal_connect (list,
533 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
534 GINT_TO_POINTER (TRUE));
535 g_signal_connect (list,
537 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
538 GINT_TO_POINTER (FALSE));
540 /* Add contacts already created */
541 contacts = empathy_contact_manager_get_contacts (priv->manager);
542 for (l = contacts; l; l = l->next) {
543 GossipContact *contact;
547 contact_list_contact_added_cb (priv->manager, contact, list);
549 g_object_unref (contact);
554 contact_list_finalize (GObject *object)
556 GossipContactListPriv *priv;
558 priv = GET_PRIV (object);
560 /* FIXME: disconnect all signals on the manager and contacts */
562 g_object_unref (priv->manager);
563 g_object_unref (priv->ui);
565 G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
569 contact_list_get_property (GObject *object,
574 GossipContactListPriv *priv;
576 priv = GET_PRIV (object);
579 case PROP_SHOW_OFFLINE:
580 g_value_set_boolean (value, priv->show_offline);
582 case PROP_SHOW_AVATARS:
583 g_value_set_boolean (value, priv->show_avatars);
585 case PROP_IS_COMPACT:
586 g_value_set_boolean (value, priv->is_compact);
589 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
595 contact_list_set_property (GObject *object,
600 GossipContactListPriv *priv;
602 priv = GET_PRIV (object);
605 case PROP_SHOW_OFFLINE:
606 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
607 g_value_get_boolean (value));
609 case PROP_SHOW_AVATARS:
610 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
611 g_value_get_boolean (value));
613 case PROP_IS_COMPACT:
614 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
615 g_value_get_boolean (value));
618 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
624 contact_list_contact_update (GossipContactList *list,
625 GossipContact *contact)
627 GossipContactListPriv *priv;
628 ShowActiveData *data;
632 gboolean should_be_in_list;
633 gboolean was_online = TRUE;
634 gboolean now_online = FALSE;
635 gboolean set_model = FALSE;
636 gboolean do_remove = FALSE;
637 gboolean do_set_active = FALSE;
638 gboolean do_set_refresh = FALSE;
639 GdkPixbuf *pixbuf_presence;
640 GdkPixbuf *pixbuf_avatar;
642 priv = GET_PRIV (list);
644 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
646 iters = contact_list_find_contact (list, contact);
653 /* Get online state now. */
654 now_online = gossip_contact_is_online (contact);
656 if (priv->show_offline || now_online) {
657 should_be_in_list = TRUE;
659 should_be_in_list = FALSE;
662 if (!in_list && !should_be_in_list) {
664 gossip_debug (DEBUG_DOMAIN,
665 "Contact:'%s' in list:NO, should be:NO",
666 gossip_contact_get_name (contact));
668 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
672 else if (in_list && !should_be_in_list) {
673 gossip_debug (DEBUG_DOMAIN,
674 "Contact:'%s' in list:YES, should be:NO",
675 gossip_contact_get_name (contact));
677 if (priv->show_active) {
679 do_set_active = TRUE;
680 do_set_refresh = TRUE;
683 gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
685 gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
686 contact_list_remove_contact (list, contact);
689 else if (!in_list && should_be_in_list) {
690 gossip_debug (DEBUG_DOMAIN,
691 "Contact:'%s' in list:NO, should be:YES",
692 gossip_contact_get_name (contact));
694 contact_list_add_contact (list, contact);
696 if (priv->show_active) {
697 do_set_active = TRUE;
699 gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
702 gossip_debug (DEBUG_DOMAIN,
703 "Contact:'%s' in list:YES, should be:YES",
704 gossip_contact_get_name (contact));
706 /* Get online state before. */
707 if (iters && g_list_length (iters) > 0) {
710 iter = g_list_nth_data (iters, 0);
711 gtk_tree_model_get (model, iter, COL_IS_ONLINE, &was_online, -1);
714 /* Is this really an update or an online/offline. */
715 if (priv->show_active) {
716 if (was_online != now_online) {
719 do_set_active = TRUE;
720 do_set_refresh = TRUE;
723 str = "online -> offline";
725 str = "offline -> online";
728 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
730 /* Was TRUE for presence updates. */
731 /* do_set_active = FALSE; */
732 do_set_refresh = TRUE;
734 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
741 pixbuf_presence = gossip_pixbuf_for_contact (contact);
742 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
743 for (l = iters; l && set_model; l = l->next) {
744 gtk_tree_store_set (GTK_TREE_STORE (model), l->data,
745 COL_PIXBUF_STATUS, pixbuf_presence,
746 COL_STATUS, gossip_contact_get_status (contact),
747 COL_IS_ONLINE, now_online,
748 COL_NAME, gossip_contact_get_name (contact),
749 COL_PIXBUF_AVATAR, pixbuf_avatar,
753 if (pixbuf_presence) {
754 g_object_unref (pixbuf_presence);
757 g_object_unref (pixbuf_avatar);
760 if (priv->show_active && do_set_active) {
761 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
764 data = contact_list_contact_active_new (list, contact, do_remove);
765 g_timeout_add (ACTIVE_USER_SHOW_TIME,
766 (GSourceFunc) contact_list_contact_active_cb,
771 /* FIXME: when someone goes online then offline quickly, the
772 * first timeout sets the user to be inactive and the second
773 * timeout removes the user from the contact list, really we
774 * should remove the first timeout.
776 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
781 contact_list_contact_added_cb (EmpathyContactManager *manager,
782 GossipContact *contact,
783 GossipContactList *list)
785 GossipContactListPriv *priv;
787 priv = GET_PRIV (list);
789 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' added",
790 gossip_contact_get_name (contact));
792 /* Connect notifications for contact updates */
793 g_signal_connect (contact, "notify::groups",
794 G_CALLBACK (contact_list_contact_groups_updated_cb),
796 g_signal_connect (contact, "notify::presence",
797 G_CALLBACK (contact_list_contact_updated_cb),
799 g_signal_connect (contact, "notify::name",
800 G_CALLBACK (contact_list_contact_updated_cb),
802 g_signal_connect (contact, "notify::avatar",
803 G_CALLBACK (contact_list_contact_updated_cb),
805 g_signal_connect (contact, "notify::type",
806 G_CALLBACK (contact_list_contact_updated_cb),
809 contact_list_add_contact (list, contact);
813 contact_list_contact_groups_updated_cb (GossipContact *contact,
815 GossipContactList *list)
817 GossipContactListPriv *priv;
819 priv = GET_PRIV (list);
821 if (priv->show_offline || gossip_contact_is_online (contact)) {
825 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
826 gossip_contact_get_name (contact));
828 /* We do this to make sure the groups are correct, if not, we
829 * would have to check the groups already set up for each
830 * contact and then see what has been updated.
832 contact_list_remove_contact (list, contact);
833 contact_list_add_contact (list, contact);
837 contact_list_contact_updated_cb (GossipContact *contact,
839 GossipContactList *list)
841 contact_list_contact_update (list, contact);
845 contact_list_contact_removed_cb (EmpathyContactManager *manager,
846 GossipContact *contact,
847 GossipContactList *list)
849 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
850 gossip_contact_get_name (contact));
852 /* Disconnect signals */
853 g_signal_handlers_disconnect_by_func (contact,
854 G_CALLBACK (contact_list_contact_groups_updated_cb),
856 g_signal_handlers_disconnect_by_func (contact,
857 G_CALLBACK (contact_list_contact_updated_cb),
860 contact_list_remove_contact (list, contact);
864 contact_list_contact_set_active (GossipContactList *list,
865 GossipContact *contact,
867 gboolean set_changed)
872 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
874 iters = contact_list_find_contact (list, contact);
875 for (l = iters; l; l = l->next) {
881 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
882 COL_IS_ACTIVE, active,
884 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
887 path = gtk_tree_model_get_path (model, iter);
888 gtk_tree_model_row_changed (model, path, iter);
889 gtk_tree_path_free (path);
893 g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
897 static ShowActiveData *
898 contact_list_contact_active_new (GossipContactList *list,
899 GossipContact *contact,
902 ShowActiveData *data;
904 g_return_val_if_fail (list != NULL, NULL);
905 g_return_val_if_fail (contact != NULL, NULL);
907 gossip_debug (DEBUG_DOMAIN,
908 "Contact:'%s' now active, and %s be removed",
909 gossip_contact_get_name (contact),
910 remove ? "WILL" : "WILL NOT");
912 data = g_slice_new0 (ShowActiveData);
914 data->list = g_object_ref (list);
915 data->contact = g_object_ref (contact);
917 data->remove = remove;
923 contact_list_contact_active_free (ShowActiveData *data)
925 g_return_if_fail (data != NULL);
927 g_object_unref (data->contact);
928 g_object_unref (data->list);
930 g_slice_free (ShowActiveData, data);
934 contact_list_contact_active_cb (ShowActiveData *data)
936 GossipContactListPriv *priv;
938 g_return_val_if_fail (data != NULL, FALSE);
940 priv = GET_PRIV (data->list);
943 !priv->show_offline &&
944 !gossip_contact_is_online (data->contact)) {
945 gossip_debug (DEBUG_DOMAIN,
946 "Contact:'%s' active timeout, removing item",
947 gossip_contact_get_name (data->contact));
948 contact_list_remove_contact (data->list,
952 gossip_debug (DEBUG_DOMAIN,
953 "Contact:'%s' no longer active",
954 gossip_contact_get_name (data->contact));
955 contact_list_contact_set_active (data->list,
960 contact_list_contact_active_free (data);
966 contact_list_get_parent_group (GtkTreeModel *model,
968 gboolean *path_is_group)
970 GtkTreeIter parent_iter, iter;
974 g_return_val_if_fail (model != NULL, NULL);
975 g_return_val_if_fail (path != NULL, NULL);
976 g_return_val_if_fail (path_is_group != NULL, NULL);
978 if (!gtk_tree_model_get_iter (model, &iter, path)) {
982 gtk_tree_model_get (model, &iter,
983 COL_IS_GROUP, &is_group,
987 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
993 gtk_tree_model_get (model, &iter,
994 COL_IS_GROUP, &is_group,
1001 *path_is_group = TRUE;
1004 gtk_tree_model_get (model, &iter,
1012 contact_list_get_group (GossipContactList *list,
1014 GtkTreeIter *iter_to_set,
1017 GtkTreeModel *model;
1020 memset (&fg, 0, sizeof (fg));
1024 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1025 gtk_tree_model_foreach (model,
1026 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1034 gtk_tree_store_append (GTK_TREE_STORE (model), iter_to_set, NULL);
1035 gtk_tree_store_set (GTK_TREE_STORE (model), iter_to_set,
1036 COL_PIXBUF_STATUS, NULL,
1039 COL_IS_ACTIVE, FALSE,
1046 *iter_to_set = fg.iter;
1051 contact_list_get_group_foreach (GtkTreeModel *model,
1059 /* Groups are only at the top level. */
1060 if (gtk_tree_path_get_depth (path) != 1) {
1064 gtk_tree_model_get (model, iter,
1066 COL_IS_GROUP, &is_group,
1068 if (is_group && strcmp (str, fg->name) == 0) {
1079 contact_list_add_contact (GossipContactList *list,
1080 GossipContact *contact)
1082 GossipContactListPriv *priv;
1083 GtkTreeIter iter, iter_group;
1084 GtkTreeModel *model;
1087 priv = GET_PRIV (list);
1089 if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1093 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1095 /* If no groups just add it at the top level. */
1096 groups = gossip_contact_get_groups (contact);
1098 GdkPixbuf *pixbuf_status;
1099 GdkPixbuf *pixbuf_avatar;
1100 gboolean show_avatar = FALSE;
1102 pixbuf_status = gossip_pixbuf_for_contact (contact);
1103 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
1106 if (priv->show_avatars && !priv->is_compact) {
1110 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, NULL);
1111 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
1112 COL_PIXBUF_STATUS, pixbuf_status,
1113 COL_PIXBUF_AVATAR, pixbuf_avatar,
1114 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1115 COL_NAME, gossip_contact_get_name (contact),
1116 COL_STATUS, gossip_contact_get_status (contact),
1117 COL_STATUS_VISIBLE, !priv->is_compact,
1118 COL_CONTACT, contact,
1119 COL_IS_GROUP, FALSE,
1120 COL_IS_ACTIVE, FALSE,
1121 COL_IS_ONLINE, gossip_contact_is_online (contact),
1124 if (pixbuf_avatar) {
1125 g_object_unref (pixbuf_avatar);
1127 if (pixbuf_status) {
1128 g_object_unref (pixbuf_status);
1132 /* Else add to each group. */
1133 for (l = groups; l; l = l->next) {
1135 GdkPixbuf *pixbuf_status;
1136 GdkPixbuf *pixbuf_avatar;
1139 gboolean show_avatar = FALSE;
1146 pixbuf_status = gossip_pixbuf_for_contact (contact);
1147 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
1150 contact_list_get_group (list, name, &iter_group, &created);
1152 if (priv->show_avatars && !priv->is_compact) {
1156 gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &iter_group);
1157 gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
1158 COL_PIXBUF_STATUS, pixbuf_status,
1159 COL_PIXBUF_AVATAR, pixbuf_avatar,
1160 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1161 COL_NAME, gossip_contact_get_name (contact),
1162 COL_STATUS, gossip_contact_get_status (contact),
1163 COL_STATUS_VISIBLE, !priv->is_compact,
1164 COL_CONTACT, contact,
1165 COL_IS_GROUP, FALSE,
1166 COL_IS_ACTIVE, FALSE,
1167 COL_IS_ONLINE, gossip_contact_is_online (contact),
1170 if (pixbuf_avatar) {
1171 g_object_unref (pixbuf_avatar);
1173 if (pixbuf_status) {
1174 g_object_unref (pixbuf_status);
1181 path = gtk_tree_model_get_path (model, &iter_group);
1186 if (gossip_contact_group_get_expanded (name)) {
1187 g_signal_handlers_block_by_func (list,
1188 contact_list_row_expand_or_collapse_cb,
1189 GINT_TO_POINTER (TRUE));
1190 gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1191 g_signal_handlers_unblock_by_func (list,
1192 contact_list_row_expand_or_collapse_cb,
1193 GINT_TO_POINTER (TRUE));
1195 g_signal_handlers_block_by_func (list,
1196 contact_list_row_expand_or_collapse_cb,
1197 GINT_TO_POINTER (FALSE));
1198 gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1199 g_signal_handlers_unblock_by_func (list,
1200 contact_list_row_expand_or_collapse_cb,
1201 GINT_TO_POINTER (FALSE));
1204 gtk_tree_path_free (path);
1209 contact_list_remove_contact (GossipContactList *list,
1210 GossipContact *contact)
1212 GossipContactListPriv *priv;
1213 GtkTreeModel *model;
1216 priv = GET_PRIV (list);
1218 iters = contact_list_find_contact (list, contact);
1223 /* Clean up model */
1224 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1226 for (l = iters; l; l = l->next) {
1229 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1230 gtk_tree_model_iter_n_children (model, &parent) <= 1) {
1231 gtk_tree_store_remove (GTK_TREE_STORE (model), &parent);
1233 gtk_tree_store_remove (GTK_TREE_STORE (model), l->data);
1237 g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
1238 g_list_free (iters);
1242 contact_list_create_model (GossipContactList *list)
1244 GtkTreeModel *model;
1246 model = GTK_TREE_MODEL (
1247 gtk_tree_store_new (COL_COUNT,
1248 GDK_TYPE_PIXBUF, /* Status pixbuf */
1249 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
1250 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
1251 G_TYPE_STRING, /* Name */
1252 G_TYPE_STRING, /* Status string */
1253 G_TYPE_BOOLEAN, /* Show status */
1254 GOSSIP_TYPE_CONTACT, /* Contact type */
1255 G_TYPE_BOOLEAN, /* Is group */
1256 G_TYPE_BOOLEAN, /* Is active */
1257 G_TYPE_BOOLEAN)); /* Is online */
1259 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1261 contact_list_sort_func,
1264 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
1266 GTK_SORT_ASCENDING);
1268 gtk_tree_view_set_model (GTK_TREE_VIEW (list), model);
1270 g_object_unref (model);
1274 contact_list_search_equal_func (GtkTreeModel *model,
1278 gpointer search_data)
1280 gchar *name, *name_folded;
1284 gtk_tree_model_get (model, iter,
1288 name_folded = g_utf8_casefold (name, -1);
1289 key_folded = g_utf8_casefold (key, -1);
1291 if (strstr (name_folded, key_folded)) {
1298 g_free (name_folded);
1299 g_free (key_folded);
1305 contact_list_setup_view (GossipContactList *list)
1307 GtkCellRenderer *cell;
1308 GtkTreeViewColumn *col;
1311 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1312 contact_list_search_equal_func,
1317 "headers-visible", FALSE,
1318 "reorderable", TRUE,
1319 "show-expanders", FALSE,
1322 col = gtk_tree_view_column_new ();
1325 cell = gtk_cell_renderer_pixbuf_new ();
1326 gtk_tree_view_column_pack_start (col, cell, FALSE);
1327 gtk_tree_view_column_set_cell_data_func (
1329 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1339 cell = gossip_cell_renderer_text_new ();
1340 gtk_tree_view_column_pack_start (col, cell, TRUE);
1341 gtk_tree_view_column_set_cell_data_func (
1343 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1346 gtk_tree_view_column_add_attribute (col, cell,
1348 gtk_tree_view_column_add_attribute (col, cell,
1349 "status", COL_STATUS);
1350 gtk_tree_view_column_add_attribute (col, cell,
1351 "is_group", COL_IS_GROUP);
1354 cell = gtk_cell_renderer_pixbuf_new ();
1355 gtk_tree_view_column_pack_start (col, cell, FALSE);
1356 gtk_tree_view_column_set_cell_data_func (
1358 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1370 cell = gossip_cell_renderer_expander_new ();
1371 gtk_tree_view_column_pack_end (col, cell, FALSE);
1372 gtk_tree_view_column_set_cell_data_func (
1374 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1377 /* Actually add the column now we have added all cell renderers */
1378 gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1381 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1382 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1386 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1387 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1391 /* Note: We support the COPY action too, but need to make the
1392 * MOVE action the default.
1394 gtk_drag_source_set (GTK_WIDGET (list),
1397 G_N_ELEMENTS (drag_types_source),
1400 gtk_drag_dest_set (GTK_WIDGET (list),
1401 GTK_DEST_DEFAULT_ALL,
1403 G_N_ELEMENTS (drag_types_dest),
1404 GDK_ACTION_MOVE | GDK_ACTION_LINK);
1406 g_signal_connect (GTK_WIDGET (list),
1407 "drag-data-received",
1408 G_CALLBACK (contact_list_drag_data_received),
1411 /* FIXME: noticed but when you drag the row over the treeview
1412 * fast, it seems to stop redrawing itself, if we don't
1413 * connect this signal, all is fine.
1415 g_signal_connect (GTK_WIDGET (list),
1417 G_CALLBACK (contact_list_drag_motion),
1420 g_signal_connect (GTK_WIDGET (list),
1422 G_CALLBACK (contact_list_drag_begin),
1424 g_signal_connect (GTK_WIDGET (list),
1426 G_CALLBACK (contact_list_drag_data_get),
1428 g_signal_connect (GTK_WIDGET (list),
1430 G_CALLBACK (contact_list_drag_end),
1435 contact_list_drag_data_received (GtkWidget *widget,
1436 GdkDragContext *context,
1439 GtkSelectionData *selection,
1444 GossipContactListPriv *priv;
1445 GtkTreeModel *model;
1447 GtkTreeViewDropPosition position;
1448 GossipContact *contact;
1453 gboolean drag_success = TRUE;
1454 gboolean drag_del = FALSE;
1456 priv = GET_PRIV (widget);
1458 id = (const gchar*) selection->data;
1459 gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1460 context->action == GDK_ACTION_MOVE ? "move" : "",
1461 context->action == GDK_ACTION_COPY ? "copy" : "",
1464 /* FIXME: This is ambigous, an id can come from multiple accounts */
1465 contact = empathy_contact_manager_find (priv->manager, id);
1467 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1471 groups = gossip_contact_get_groups (contact);
1473 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1480 if (g_list_length (groups) != 1) {
1481 /* if they have dragged a contact out of a
1482 * group then we would set the contact to have
1483 * NO groups but only if they were ONE group
1484 * to begin with - should we do this
1485 * regardless to how many groups they are in
1486 * already or not at all?
1491 gossip_contact_set_groups (contact, NULL);
1493 GList *l, *new_groups;
1497 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1498 name = contact_list_get_parent_group (model, path, &is_group);
1500 if (groups && name &&
1501 g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1506 /* Get source group information. */
1507 priv = GET_PRIV (widget);
1508 if (!priv->drag_row) {
1513 path = gtk_tree_row_reference_get_path (priv->drag_row);
1519 old_group = contact_list_get_parent_group (model, path, &is_group);
1520 gtk_tree_path_free (path);
1522 if (!name && old_group && GDK_ACTION_MOVE) {
1523 drag_success = FALSE;
1526 if (context->action == GDK_ACTION_MOVE) {
1530 /* Create new groups GList. */
1531 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1535 if (context->action == GDK_ACTION_MOVE &&
1536 old_group != NULL &&
1537 strcmp (str, old_group) == 0) {
1545 new_groups = g_list_append (new_groups, g_strdup (str));
1550 new_groups = g_list_append (new_groups, name);
1552 gossip_contact_set_groups (contact, new_groups);
1558 gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1562 contact_list_drag_motion (GtkWidget *widget,
1563 GdkDragContext *context,
1569 static DragMotionData *dm = NULL;
1572 gboolean is_different = FALSE;
1573 gboolean cleanup = TRUE;
1575 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1586 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1587 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1592 if (!is_different && !cleanup) {
1597 gtk_tree_path_free (dm->path);
1598 if (dm->timeout_id) {
1599 g_source_remove (dm->timeout_id);
1607 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1608 dm = g_new0 (DragMotionData, 1);
1610 dm->list = GOSSIP_CONTACT_LIST (widget);
1611 dm->path = gtk_tree_path_copy (path);
1613 dm->timeout_id = g_timeout_add (
1615 (GSourceFunc) contact_list_drag_motion_cb,
1623 contact_list_drag_motion_cb (DragMotionData *data)
1625 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1629 data->timeout_id = 0;
1635 contact_list_drag_begin (GtkWidget *widget,
1636 GdkDragContext *context,
1639 GossipContactListPriv *priv;
1640 GtkTreeSelection *selection;
1641 GtkTreeModel *model;
1645 priv = GET_PRIV (widget);
1647 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1648 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1652 path = gtk_tree_model_get_path (model, &iter);
1653 priv->drag_row = gtk_tree_row_reference_new (model, path);
1654 gtk_tree_path_free (path);
1658 contact_list_drag_data_get (GtkWidget *widget,
1659 GdkDragContext *context,
1660 GtkSelectionData *selection,
1665 GossipContactListPriv *priv;
1666 GtkTreePath *src_path;
1668 GtkTreeModel *model;
1669 GossipContact *contact;
1672 priv = GET_PRIV (widget);
1674 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1675 if (!priv->drag_row) {
1679 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1684 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1685 gtk_tree_path_free (src_path);
1689 gtk_tree_path_free (src_path);
1691 contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1696 id = gossip_contact_get_id (contact);
1697 g_object_unref (contact);
1700 case DND_DRAG_TYPE_CONTACT_ID:
1701 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1702 (guchar*)id, strlen (id) + 1);
1711 contact_list_drag_end (GtkWidget *widget,
1712 GdkDragContext *context,
1715 GossipContactListPriv *priv;
1717 priv = GET_PRIV (widget);
1719 if (priv->drag_row) {
1720 gtk_tree_row_reference_free (priv->drag_row);
1721 priv->drag_row = NULL;
1726 contact_list_cell_set_background (GossipContactList *list,
1727 GtkCellRenderer *cell,
1733 gint color_sum_normal, color_sum_selected;
1735 g_return_if_fail (list != NULL);
1736 g_return_if_fail (cell != NULL);
1738 style = gtk_widget_get_style (GTK_WIDGET (list));
1742 color = style->bg[GTK_STATE_SELECTED];
1744 /* Here we take the current theme colour and add it to
1745 * the colour for white and average the two. This
1746 * gives a colour which is inline with the theme but
1749 color.red = (color.red + (style->white).red) / 2;
1750 color.green = (color.green + (style->white).green) / 2;
1751 color.blue = (color.blue + (style->white).blue) / 2;
1754 "cell-background-gdk", &color,
1758 "cell-background-gdk", NULL,
1762 color = style->base[GTK_STATE_SELECTED];
1763 color_sum_normal = color.red+color.green+color.blue;
1764 color = style->base[GTK_STATE_NORMAL];
1765 color_sum_selected = color.red+color.green+color.blue;
1766 color = style->text_aa[GTK_STATE_INSENSITIVE];
1768 if(color_sum_normal < color_sum_selected) {
1769 /* found a light theme */
1770 color.red = (color.red + (style->white).red) / 2;
1771 color.green = (color.green + (style->white).green) / 2;
1772 color.blue = (color.blue + (style->white).blue) / 2;
1774 /* found a dark theme */
1775 color.red = (color.red + (style->black).red) / 2;
1776 color.green = (color.green + (style->black).green) / 2;
1777 color.blue = (color.blue + (style->black).blue) / 2;
1781 "cell-background-gdk", &color,
1787 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1788 GtkCellRenderer *cell,
1789 GtkTreeModel *model,
1791 GossipContactList *list)
1797 gtk_tree_model_get (model, iter,
1798 COL_IS_GROUP, &is_group,
1799 COL_IS_ACTIVE, &is_active,
1800 COL_PIXBUF_STATUS, &pixbuf,
1804 "visible", !is_group,
1809 g_object_unref (pixbuf);
1812 contact_list_cell_set_background (list, cell, is_group, is_active);
1816 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1817 GtkCellRenderer *cell,
1818 GtkTreeModel *model,
1820 GossipContactList *list)
1823 gboolean show_avatar;
1827 gtk_tree_model_get (model, iter,
1828 COL_PIXBUF_AVATAR, &pixbuf,
1829 COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1830 COL_IS_GROUP, &is_group,
1831 COL_IS_ACTIVE, &is_active,
1835 "visible", !is_group && show_avatar,
1840 g_object_unref (pixbuf);
1843 contact_list_cell_set_background (list, cell, is_group, is_active);
1847 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1848 GtkCellRenderer *cell,
1849 GtkTreeModel *model,
1851 GossipContactList *list)
1855 gboolean show_status;
1857 gtk_tree_model_get (model, iter,
1858 COL_IS_GROUP, &is_group,
1859 COL_IS_ACTIVE, &is_active,
1860 COL_STATUS_VISIBLE, &show_status,
1864 "show-status", show_status,
1867 contact_list_cell_set_background (list, cell, is_group, is_active);
1871 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1872 GtkCellRenderer *cell,
1873 GtkTreeModel *model,
1875 GossipContactList *list)
1880 gtk_tree_model_get (model, iter,
1881 COL_IS_GROUP, &is_group,
1882 COL_IS_ACTIVE, &is_active,
1885 if (gtk_tree_model_iter_has_child (model, iter)) {
1887 gboolean row_expanded;
1889 path = gtk_tree_model_get_path (model, iter);
1890 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
1891 gtk_tree_path_free (path);
1895 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1898 g_object_set (cell, "visible", FALSE, NULL);
1901 contact_list_cell_set_background (list, cell, is_group, is_active);
1905 contact_list_get_contact_menu (GossipContactList *list,
1906 gboolean can_send_file,
1907 gboolean can_show_log)
1909 GossipContactListPriv *priv;
1913 priv = GET_PRIV (list);
1915 /* Sort out sensitive items */
1916 action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
1917 gtk_action_set_sensitive (action, can_show_log);
1919 action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
1920 gtk_action_set_visible (action, can_send_file);
1922 widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
1928 gossip_contact_list_get_group_menu (GossipContactList *list)
1930 GossipContactListPriv *priv;
1933 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
1935 priv = GET_PRIV (list);
1937 widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
1943 gossip_contact_list_get_contact_menu (GossipContactList *list,
1944 GossipContact *contact)
1947 gboolean can_show_log;
1948 gboolean can_send_file;
1950 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
1951 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
1953 can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
1954 can_send_file = FALSE;
1956 menu = contact_list_get_contact_menu (list,
1963 contact_list_button_press_event_cb (GossipContactList *list,
1964 GdkEventButton *event,
1967 GossipContactListPriv *priv;
1968 GossipContact *contact;
1970 GtkTreeSelection *selection;
1971 GtkTreeModel *model;
1973 gboolean row_exists;
1976 if (event->button != 3) {
1980 priv = GET_PRIV (list);
1982 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
1983 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1985 gtk_widget_grab_focus (GTK_WIDGET (list));
1987 row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
1995 gtk_tree_selection_unselect_all (selection);
1996 gtk_tree_selection_select_path (selection, path);
1998 gtk_tree_model_get_iter (model, &iter, path);
1999 gtk_tree_path_free (path);
2001 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2004 menu = gossip_contact_list_get_contact_menu (list, contact);
2005 g_object_unref (contact);
2007 menu = gossip_contact_list_get_group_menu (list);
2014 gtk_widget_show (menu);
2016 gtk_menu_popup (GTK_MENU (menu),
2017 NULL, NULL, NULL, NULL,
2018 event->button, event->time);
2024 contact_list_row_activated_cb (GossipContactList *list,
2026 GtkTreeViewColumn *col,
2029 GossipContact *contact;
2031 GtkTreeModel *model;
2034 view = GTK_TREE_VIEW (list);
2035 model = gtk_tree_view_get_model (view);
2037 gtk_tree_model_get_iter (model, &iter, path);
2038 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2041 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2042 g_object_unref (contact);
2047 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2052 GtkTreeModel *model;
2056 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2058 gtk_tree_model_get (model, iter,
2062 expanded = GPOINTER_TO_INT (user_data);
2063 gossip_contact_group_set_expanded (name, expanded);
2069 contact_list_sort_func (GtkTreeModel *model,
2070 GtkTreeIter *iter_a,
2071 GtkTreeIter *iter_b,
2074 gchar *name_a, *name_b;
2075 GossipContact *contact_a, *contact_b;
2078 gtk_tree_model_get (model, iter_a,
2080 COL_CONTACT, &contact_a,
2082 gtk_tree_model_get (model, iter_b,
2084 COL_CONTACT, &contact_b,
2087 /* If contact is NULL it means it's a group. */
2089 if (!contact_a && contact_b) {
2091 } else if (contact_a && !contact_b) {
2094 ret_val = g_utf8_collate (name_a, name_b);
2101 g_object_unref (contact_a);
2105 g_object_unref (contact_b);
2112 contact_list_iter_equal_contact (GtkTreeModel *model,
2114 GossipContact *contact)
2119 gtk_tree_model_get (model, iter,
2127 equal = (c == contact);
2134 contact_list_find_contact (GossipContactList *list,
2135 GossipContact *contact)
2137 GtkTreeModel *model;
2141 memset (&fc, 0, sizeof (fc));
2143 fc.contact = contact;
2145 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2146 gtk_tree_model_foreach (model,
2147 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2158 contact_list_find_contact_foreach (GtkTreeModel *model,
2163 if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2165 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2168 /* We want to find ALL contacts that match, this means if we
2169 * have the same contact in 3 groups, all iters should be
2176 contact_list_action_cb (GtkAction *action,
2177 GossipContactList *list)
2179 GossipContact *contact;
2183 name = gtk_action_get_name (action);
2188 gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2190 contact = gossip_contact_list_get_selected (list);
2191 group = gossip_contact_list_get_selected_group (list);
2193 if (contact && strcmp (name, "Chat") == 0) {
2194 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2196 else if (contact && strcmp (name, "Information") == 0) {
2197 g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
2199 else if (contact && strcmp (name, "Edit") == 0) {
2200 g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
2202 else if (contact && strcmp (name, "Remove") == 0) {
2203 g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
2205 else if (contact && strcmp (name, "Invite") == 0) {
2206 g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
2208 else if (contact && strcmp (name, "SendFile") == 0) {
2209 g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
2211 else if (contact && strcmp (name, "Log") == 0) {
2212 g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
2214 else if (group && strcmp (name, "Rename") == 0) {
2215 g_signal_emit (list, signals[GROUP_RENAME], 0, group);
2220 g_object_unref (contact);
2225 contact_list_update_list_mode_foreach (GtkTreeModel *model,
2228 GossipContactList *list)
2230 GossipContactListPriv *priv;
2231 gboolean show_avatar = FALSE;
2233 priv = GET_PRIV (list);
2235 if (priv->show_avatars && !priv->is_compact) {
2239 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
2240 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2241 COL_STATUS_VISIBLE, !priv->is_compact,
2248 gossip_contact_list_new (void)
2250 return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2254 gossip_contact_list_get_selected (GossipContactList *list)
2256 GossipContactListPriv *priv;
2257 GtkTreeSelection *selection;
2259 GtkTreeModel *model;
2260 GossipContact *contact;
2262 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2264 priv = GET_PRIV (list);
2266 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2267 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2271 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2277 gossip_contact_list_get_selected_group (GossipContactList *list)
2279 GossipContactListPriv *priv;
2280 GtkTreeSelection *selection;
2282 GtkTreeModel *model;
2286 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2288 priv = GET_PRIV (list);
2290 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2291 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2295 gtk_tree_model_get (model, &iter,
2296 COL_IS_GROUP, &is_group,
2309 gossip_contact_list_get_show_offline (GossipContactList *list)
2311 GossipContactListPriv *priv;
2313 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2315 priv = GET_PRIV (list);
2317 return priv->show_offline;
2321 gossip_contact_list_set_show_offline (GossipContactList *list,
2322 gboolean show_offline)
2324 GossipContactListPriv *priv;
2325 GList *contacts, *l;
2326 gboolean show_active;
2328 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2330 priv = GET_PRIV (list);
2332 priv->show_offline = show_offline;
2333 show_active = priv->show_active;
2335 /* Disable temporarily. */
2336 priv->show_active = FALSE;
2338 contacts = empathy_contact_manager_get_contacts (priv->manager);
2339 for (l = contacts; l; l = l->next) {
2340 GossipContact *contact;
2342 contact = GOSSIP_CONTACT (l->data);
2344 contact_list_contact_update (list, contact);
2346 g_object_unref (contact);
2348 g_list_free (contacts);
2350 /* Restore to original setting. */
2351 priv->show_active = show_active;
2355 gossip_contact_list_get_show_avatars (GossipContactList *list)
2357 GossipContactListPriv *priv;
2359 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2361 priv = GET_PRIV (list);
2363 return priv->show_avatars;
2367 gossip_contact_list_set_show_avatars (GossipContactList *list,
2368 gboolean show_avatars)
2370 GossipContactListPriv *priv;
2371 GtkTreeModel *model;
2373 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2375 priv = GET_PRIV (list);
2377 priv->show_avatars = show_avatars;
2379 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2381 gtk_tree_model_foreach (model,
2382 (GtkTreeModelForeachFunc)
2383 contact_list_update_list_mode_foreach,
2388 gossip_contact_list_get_is_compact (GossipContactList *list)
2390 GossipContactListPriv *priv;
2392 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2394 priv = GET_PRIV (list);
2396 return priv->is_compact;
2400 gossip_contact_list_set_is_compact (GossipContactList *list,
2401 gboolean is_compact)
2403 GossipContactListPriv *priv;
2404 GtkTreeModel *model;
2406 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2408 priv = GET_PRIV (list);
2410 priv->is_compact = is_compact;
2412 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2414 gtk_tree_model_foreach (model,
2415 (GtkTreeModelForeachFunc)
2416 contact_list_update_list_mode_foreach,