1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2005-2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
20 * Authors: Mikael Hallendal <micke@imendio.com>
21 * Martyn Russell <martyn@imendio.com>
28 #include <glib/gi18n.h>
30 #include <glade/glade.h>
32 #include <libmissioncontrol/mc-account.h>
33 #include <libmissioncontrol/mission-control.h>
35 #include <libempathy/empathy-contact-list.h>
36 #include <libempathy/empathy-contact-manager.h>
37 #include <libempathy/gossip-debug.h>
38 #include <libempathy/gossip-utils.h>
40 #include "empathy-images.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-ui-utils.h"
46 //#include "gossip-chat-invite.h"
47 //#include "gossip-contact-info-dialog.h"
48 //#include "gossip-edit-contact-dialog.h"
49 //#include "gossip-ft-window.h"
50 //#include "gossip-log-window.h"
52 #define DEBUG_DOMAIN "ContactListUI"
54 /* Flashing delay for icons (milliseconds). */
55 #define FLASH_TIMEOUT 500
57 /* Active users are those which have recently changed state
58 * (e.g. online, offline or from normal to a busy state).
61 /* Time user is shown as active */
62 #define ACTIVE_USER_SHOW_TIME 7000
64 /* Time after connecting which we wait before active users are enabled */
65 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
67 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
69 struct _GossipContactListPriv {
70 EmpathyContactList *list;
75 GtkTreeRowReference *drag_row;
81 gboolean show_offline;
82 gboolean show_avatars;
86 GossipContactListSort sort_criterium;
96 GossipContact *contact;
102 GossipContactList *list;
108 GossipContactList *list;
109 GossipContact *contact;
113 static void gossip_contact_list_class_init (GossipContactListClass *klass);
114 static void gossip_contact_list_init (GossipContactList *list);
115 static void contact_list_finalize (GObject *object);
116 static void contact_list_get_property (GObject *object,
120 static void contact_list_set_property (GObject *object,
124 static gboolean contact_list_row_separator_func (GtkTreeModel *model,
127 static void contact_list_contact_update (GossipContactList *list,
128 GossipContact *contact);
129 static void contact_list_contact_added_cb (EmpathyContactList *list_iface,
130 GossipContact *contact,
131 GossipContactList *list);
132 static void contact_list_contact_updated_cb (GossipContact *contact,
134 GossipContactList *list);
135 static void contact_list_contact_groups_updated_cb (GossipContact *contact,
137 GossipContactList *list);
138 static void contact_list_contact_removed_cb (EmpathyContactList *list_iface,
139 GossipContact *contact,
140 GossipContactList *list);
141 static void contact_list_contact_set_active (GossipContactList *list,
142 GossipContact *contact,
144 gboolean set_changed);
145 static ShowActiveData *
146 contact_list_contact_active_new (GossipContactList *list,
147 GossipContact *contact,
149 static void contact_list_contact_active_free (ShowActiveData *data);
150 static gboolean contact_list_contact_active_cb (ShowActiveData *data);
151 static gchar * contact_list_get_parent_group (GtkTreeModel *model,
153 gboolean *path_is_group);
154 static void contact_list_get_group (GossipContactList *list,
156 GtkTreeIter *iter_group_to_set,
157 GtkTreeIter *iter_separator_to_set,
159 static gboolean contact_list_get_group_foreach (GtkTreeModel *model,
163 static void contact_list_add_contact (GossipContactList *list,
164 GossipContact *contact);
165 static void contact_list_remove_contact (GossipContactList *list,
166 GossipContact *contact);
167 static void contact_list_create_model (GossipContactList *list);
168 static gboolean contact_list_search_equal_func (GtkTreeModel *model,
172 gpointer search_data);
173 static void contact_list_setup_view (GossipContactList *list);
174 static void contact_list_drag_data_received (GtkWidget *widget,
175 GdkDragContext *context,
178 GtkSelectionData *selection,
182 static gboolean contact_list_drag_motion (GtkWidget *widget,
183 GdkDragContext *context,
188 static gboolean contact_list_drag_motion_cb (DragMotionData *data);
189 static void contact_list_drag_begin (GtkWidget *widget,
190 GdkDragContext *context,
192 static void contact_list_drag_data_get (GtkWidget *widget,
193 GdkDragContext *contact,
194 GtkSelectionData *selection,
198 static void contact_list_drag_end (GtkWidget *widget,
199 GdkDragContext *context,
201 static void contact_list_cell_set_background (GossipContactList *list,
202 GtkCellRenderer *cell,
205 static void contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
206 GtkCellRenderer *cell,
209 GossipContactList *list);
210 static void contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
211 GtkCellRenderer *cell,
214 GossipContactList *list);
215 static void contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
216 GtkCellRenderer *cell,
219 GossipContactList *list);
220 static void contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
221 GtkCellRenderer *cell,
224 GossipContactList *list);
225 static GtkWidget *contact_list_get_contact_menu (GossipContactList *list,
226 gboolean can_send_file,
227 gboolean can_show_log);
228 static gboolean contact_list_button_press_event_cb (GossipContactList *list,
229 GdkEventButton *event,
231 static void contact_list_row_activated_cb (GossipContactList *list,
233 GtkTreeViewColumn *col,
235 static void contact_list_row_expand_or_collapse_cb (GossipContactList *list,
239 static gint contact_list_name_sort_func (GtkTreeModel *model,
243 static gint contact_list_state_sort_func (GtkTreeModel *model,
247 static gboolean contact_list_filter_func (GtkTreeModel *model,
249 GossipContactList *list);
250 static GList * contact_list_find_contact (GossipContactList *list,
251 GossipContact *contact);
252 static gboolean contact_list_find_contact_foreach (GtkTreeModel *model,
256 static void contact_list_action_cb (GtkAction *action,
257 GossipContactList *list);
258 static void contact_list_action_activated (GossipContactList *list,
259 GossipContact *contact);
260 static gboolean contact_list_update_list_mode_foreach (GtkTreeModel *model,
263 GossipContactList *list);
268 COL_PIXBUF_AVATAR_VISIBLE,
289 static const GtkActionEntry entries[] = {
290 { "ContactMenu", NULL,
291 N_("_Contact"), NULL, NULL,
295 N_("_Group"),NULL, NULL,
298 { "Chat", EMPATHY_IMAGE_MESSAGE,
299 N_("_Chat"), NULL, N_("Chat with contact"),
300 G_CALLBACK (contact_list_action_cb)
302 { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
303 N_("Infor_mation"), "<control>I", N_("View contact information"),
304 G_CALLBACK (contact_list_action_cb)
307 N_("Re_name"), NULL, N_("Rename"),
308 G_CALLBACK (contact_list_action_cb)
310 { "Edit", GTK_STOCK_EDIT,
311 N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
312 G_CALLBACK (contact_list_action_cb)
314 { "Remove", GTK_STOCK_REMOVE,
315 N_("_Remove"), NULL, N_("Remove contact"),
316 G_CALLBACK (contact_list_action_cb)
318 { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
319 N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
320 G_CALLBACK (contact_list_action_cb)
323 N_("_Send File..."), NULL, N_("Send a file"),
324 G_CALLBACK (contact_list_action_cb)
326 { "Log", GTK_STOCK_JUSTIFY_LEFT,
327 N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
328 G_CALLBACK (contact_list_action_cb)
332 static guint n_entries = G_N_ELEMENTS (entries);
334 static const gchar *ui_info =
336 " <popup name='Contact'>"
337 " <menuitem action='Chat'/>"
338 " <menuitem action='Log'/>"
339 " <menuitem action='SendFile'/>"
341 " <menuitem action='Invite'/>"
343 " <menuitem action='Edit'/>"
344 " <menuitem action='Remove'/>"
346 " <menuitem action='Information'/>"
348 " <popup name='Group'>"
349 " <menuitem action='Rename'/>"
354 DND_DRAG_TYPE_CONTACT_ID,
356 DND_DRAG_TYPE_STRING,
359 static const GtkTargetEntry drag_types_dest[] = {
360 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
361 { "text/uri-list", 0, DND_DRAG_TYPE_URL },
362 { "text/plain", 0, DND_DRAG_TYPE_STRING },
363 { "STRING", 0, DND_DRAG_TYPE_STRING },
366 static const GtkTargetEntry drag_types_source[] = {
367 { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
370 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
371 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
374 gossip_contact_list_sort_get_type (void)
376 static GType etype = 0;
379 static const GEnumValue values[] = {
380 { GOSSIP_CONTACT_LIST_SORT_NAME,
381 "GOSSIP_CONTACT_LIST_SORT_NAME",
383 { GOSSIP_CONTACT_LIST_SORT_STATE,
384 "GOSSIP_CONTACT_LIST_SORT_STATE",
389 etype = g_enum_register_static ("GossipContactListSort", values);
395 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
398 gossip_contact_list_class_init (GossipContactListClass *klass)
400 GObjectClass *object_class = G_OBJECT_CLASS (klass);
402 object_class->finalize = contact_list_finalize;
403 object_class->get_property = contact_list_get_property;
404 object_class->set_property = contact_list_set_property;
406 g_object_class_install_property (object_class,
408 g_param_spec_boolean ("show-offline",
410 "Whether contact list should display "
414 g_object_class_install_property (object_class,
416 g_param_spec_boolean ("show-avatars",
418 "Whether contact list should display "
419 "avatars for contacts",
422 g_object_class_install_property (object_class,
424 g_param_spec_boolean ("is-compact",
426 "Whether the contact list is in compact mode or not",
430 g_object_class_install_property (object_class,
432 g_param_spec_string ("filter",
434 "The text to use to filter the contact list",
438 g_object_class_install_property (object_class,
440 g_param_spec_enum ("sort-criterium",
442 "The sort criterium to use for sorting the contact list",
443 GOSSIP_TYPE_CONTACT_LIST_SORT,
444 GOSSIP_CONTACT_LIST_SORT_NAME,
447 g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
451 gossip_contact_list_init (GossipContactList *list)
453 GossipContactListPriv *priv;
454 GtkActionGroup *action_group;
456 GError *error = NULL;
458 priv = GET_PRIV (list);
460 priv->list = EMPATHY_CONTACT_LIST (empathy_contact_manager_new ());
461 priv->is_compact = FALSE;
462 priv->show_active = TRUE;
463 priv->show_avatars = TRUE;
465 contact_list_create_model (list);
466 contact_list_setup_view (list);
467 empathy_contact_list_setup (priv->list);
469 /* Get saved group states. */
470 gossip_contact_groups_get_all ();
472 /* Set up UI Manager */
473 priv->ui = gtk_ui_manager_new ();
475 action_group = gtk_action_group_new ("Actions");
476 gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
477 gtk_action_group_add_actions (action_group, entries, n_entries, list);
478 gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
480 if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
481 g_warning ("Could not build contact menus from string:'%s'", error->message);
482 g_error_free (error);
485 g_object_unref (action_group);
487 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list),
488 contact_list_row_separator_func,
491 /* Signal connection. */
492 g_signal_connect (priv->list,
494 G_CALLBACK (contact_list_contact_added_cb),
496 g_signal_connect (priv->list,
498 G_CALLBACK (contact_list_contact_removed_cb),
501 /* Connect to tree view signals rather than override. */
502 g_signal_connect (list,
503 "button-press-event",
504 G_CALLBACK (contact_list_button_press_event_cb),
506 g_signal_connect (list,
508 G_CALLBACK (contact_list_row_activated_cb),
510 g_signal_connect (list,
512 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
513 GINT_TO_POINTER (TRUE));
514 g_signal_connect (list,
516 G_CALLBACK (contact_list_row_expand_or_collapse_cb),
517 GINT_TO_POINTER (FALSE));
519 /* Add contacts already created */
520 contacts = empathy_contact_list_get_contacts (priv->list);
521 for (l = contacts; l; l = l->next) {
522 GossipContact *contact;
526 contact_list_contact_added_cb (priv->list, contact, list);
528 g_object_unref (contact);
530 g_list_free (contacts);
534 contact_list_finalize (GObject *object)
536 GossipContactListPriv *priv;
538 priv = GET_PRIV (object);
540 /* FIXME: disconnect all signals on the list and contacts */
542 g_object_unref (priv->list);
543 g_object_unref (priv->ui);
544 g_object_unref (priv->store);
545 g_object_unref (priv->filter);
546 g_free (priv->filter_text);
548 G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
552 contact_list_get_property (GObject *object,
557 GossipContactListPriv *priv;
559 priv = GET_PRIV (object);
562 case PROP_SHOW_OFFLINE:
563 g_value_set_boolean (value, priv->show_offline);
565 case PROP_SHOW_AVATARS:
566 g_value_set_boolean (value, priv->show_avatars);
568 case PROP_IS_COMPACT:
569 g_value_set_boolean (value, priv->is_compact);
572 g_value_set_string (value, priv->filter_text);
574 case PROP_SORT_CRITERIUM:
575 g_value_set_enum (value, priv->sort_criterium);
578 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
584 contact_list_set_property (GObject *object,
589 GossipContactListPriv *priv;
591 priv = GET_PRIV (object);
594 case PROP_SHOW_OFFLINE:
595 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
596 g_value_get_boolean (value));
598 case PROP_SHOW_AVATARS:
599 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
600 g_value_get_boolean (value));
602 case PROP_IS_COMPACT:
603 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
604 g_value_get_boolean (value));
607 gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
608 g_value_get_string (value));
610 case PROP_SORT_CRITERIUM:
611 gossip_contact_list_set_sort_criterium (GOSSIP_CONTACT_LIST (object),
612 g_value_get_enum (value));
615 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
621 contact_list_row_separator_func (GtkTreeModel *model,
625 gboolean is_separator = FALSE;
627 gtk_tree_model_get (model, iter,
628 COL_IS_SEPARATOR, &is_separator,
635 contact_list_contact_update (GossipContactList *list,
636 GossipContact *contact)
638 GossipContactListPriv *priv;
639 ShowActiveData *data;
643 gboolean should_be_in_list;
644 gboolean was_online = TRUE;
645 gboolean now_online = FALSE;
646 gboolean set_model = FALSE;
647 gboolean do_remove = FALSE;
648 gboolean do_set_active = FALSE;
649 gboolean do_set_refresh = FALSE;
650 GdkPixbuf *pixbuf_avatar;
652 priv = GET_PRIV (list);
654 model = GTK_TREE_MODEL (priv->store);
656 iters = contact_list_find_contact (list, contact);
663 /* Get online state now. */
664 now_online = gossip_contact_is_online (contact);
666 if (priv->show_offline || now_online) {
667 should_be_in_list = TRUE;
669 should_be_in_list = FALSE;
672 if (!in_list && !should_be_in_list) {
674 gossip_debug (DEBUG_DOMAIN,
675 "Contact:'%s' in list:NO, should be:NO",
676 gossip_contact_get_name (contact));
678 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
682 else if (in_list && !should_be_in_list) {
683 gossip_debug (DEBUG_DOMAIN,
684 "Contact:'%s' in list:YES, should be:NO",
685 gossip_contact_get_name (contact));
687 if (priv->show_active) {
689 do_set_active = TRUE;
690 do_set_refresh = TRUE;
693 gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
695 gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
696 contact_list_remove_contact (list, contact);
699 else if (!in_list && should_be_in_list) {
700 gossip_debug (DEBUG_DOMAIN,
701 "Contact:'%s' in list:NO, should be:YES",
702 gossip_contact_get_name (contact));
704 contact_list_add_contact (list, contact);
706 if (priv->show_active) {
707 do_set_active = TRUE;
709 gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
712 gossip_debug (DEBUG_DOMAIN,
713 "Contact:'%s' in list:YES, should be:YES",
714 gossip_contact_get_name (contact));
716 /* Get online state before. */
717 if (iters && g_list_length (iters) > 0) {
718 gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
721 /* Is this really an update or an online/offline. */
722 if (priv->show_active) {
723 if (was_online != now_online) {
726 do_set_active = TRUE;
727 do_set_refresh = TRUE;
730 str = "online -> offline";
732 str = "offline -> online";
735 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
737 /* Was TRUE for presence updates. */
738 /* do_set_active = FALSE; */
739 do_set_refresh = TRUE;
741 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
748 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
749 for (l = iters; l && set_model; l = l->next) {
750 gtk_tree_store_set (priv->store, l->data,
751 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
752 COL_STATUS, gossip_contact_get_status (contact),
753 COL_IS_ONLINE, now_online,
754 COL_NAME, gossip_contact_get_name (contact),
755 COL_PIXBUF_AVATAR, pixbuf_avatar,
760 g_object_unref (pixbuf_avatar);
763 if (priv->show_active && do_set_active) {
764 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
767 data = contact_list_contact_active_new (list, contact, do_remove);
768 g_timeout_add (ACTIVE_USER_SHOW_TIME,
769 (GSourceFunc) contact_list_contact_active_cb,
774 /* FIXME: when someone goes online then offline quickly, the
775 * first timeout sets the user to be inactive and the second
776 * timeout removes the user from the contact list, really we
777 * should remove the first timeout.
779 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
784 contact_list_contact_added_cb (EmpathyContactList *list_iface,
785 GossipContact *contact,
786 GossipContactList *list)
788 GossipContactListPriv *priv;
790 priv = GET_PRIV (list);
792 gossip_debug (DEBUG_DOMAIN,
793 "Contact:'%s' added",
794 gossip_contact_get_name (contact));
796 g_signal_connect (contact, "notify::groups",
797 G_CALLBACK (contact_list_contact_groups_updated_cb),
799 g_signal_connect (contact, "notify::presence",
800 G_CALLBACK (contact_list_contact_updated_cb),
802 g_signal_connect (contact, "notify::name",
803 G_CALLBACK (contact_list_contact_updated_cb),
805 g_signal_connect (contact, "notify::avatar",
806 G_CALLBACK (contact_list_contact_updated_cb),
808 g_signal_connect (contact, "notify::type",
809 G_CALLBACK (contact_list_contact_updated_cb),
812 contact_list_add_contact (list, contact);
816 contact_list_contact_groups_updated_cb (GossipContact *contact,
818 GossipContactList *list)
820 GossipContactListPriv *priv;
822 priv = GET_PRIV (list);
824 if (priv->show_offline || gossip_contact_is_online (contact)) {
828 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
829 gossip_contact_get_name (contact));
831 /* We do this to make sure the groups are correct, if not, we
832 * would have to check the groups already set up for each
833 * contact and then see what has been updated.
835 contact_list_remove_contact (list, contact);
836 contact_list_add_contact (list, contact);
840 contact_list_contact_updated_cb (GossipContact *contact,
842 GossipContactList *list)
844 gossip_debug (DEBUG_DOMAIN,
845 "Contact:'%s' updated, checking roster is in sync...",
846 gossip_contact_get_name (contact));
848 contact_list_contact_update (list, contact);
852 contact_list_contact_removed_cb (EmpathyContactList *list_iface,
853 GossipContact *contact,
854 GossipContactList *list)
856 gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
857 gossip_contact_get_name (contact));
859 /* Disconnect signals */
860 g_signal_handlers_disconnect_by_func (contact,
861 G_CALLBACK (contact_list_contact_groups_updated_cb),
863 g_signal_handlers_disconnect_by_func (contact,
864 G_CALLBACK (contact_list_contact_updated_cb),
867 contact_list_remove_contact (list, contact);
871 contact_list_contact_set_active (GossipContactList *list,
872 GossipContact *contact,
874 gboolean set_changed)
876 GossipContactListPriv *priv;
880 priv = GET_PRIV (list);
882 model = GTK_TREE_MODEL (priv->store);
884 iters = contact_list_find_contact (list, contact);
885 for (l = iters; l; l = l->next) {
888 gtk_tree_store_set (priv->store, l->data,
889 COL_IS_ACTIVE, active,
892 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
895 path = gtk_tree_model_get_path (model, l->data);
896 gtk_tree_model_row_changed (model, path, l->data);
897 gtk_tree_path_free (path);
901 g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
906 static ShowActiveData *
907 contact_list_contact_active_new (GossipContactList *list,
908 GossipContact *contact,
911 ShowActiveData *data;
913 g_return_val_if_fail (list != NULL, NULL);
914 g_return_val_if_fail (contact != NULL, NULL);
916 gossip_debug (DEBUG_DOMAIN,
917 "Contact:'%s' now active, and %s be removed",
918 gossip_contact_get_name (contact),
919 remove ? "WILL" : "WILL NOT");
921 data = g_slice_new0 (ShowActiveData);
923 data->list = g_object_ref (list);
924 data->contact = g_object_ref (contact);
926 data->remove = remove;
932 contact_list_contact_active_free (ShowActiveData *data)
934 g_return_if_fail (data != NULL);
936 g_object_unref (data->contact);
937 g_object_unref (data->list);
939 g_slice_free (ShowActiveData, data);
943 contact_list_contact_active_cb (ShowActiveData *data)
945 GossipContactListPriv *priv;
947 g_return_val_if_fail (data != NULL, FALSE);
949 priv = GET_PRIV (data->list);
952 !priv->show_offline &&
953 !gossip_contact_is_online (data->contact)) {
954 gossip_debug (DEBUG_DOMAIN,
955 "Contact:'%s' active timeout, removing item",
956 gossip_contact_get_name (data->contact));
957 contact_list_remove_contact (data->list,
961 gossip_debug (DEBUG_DOMAIN,
962 "Contact:'%s' no longer active",
963 gossip_contact_get_name (data->contact));
964 contact_list_contact_set_active (data->list,
969 contact_list_contact_active_free (data);
975 contact_list_get_parent_group (GtkTreeModel *model,
977 gboolean *path_is_group)
979 GtkTreeIter parent_iter, iter;
983 g_return_val_if_fail (model != NULL, NULL);
984 g_return_val_if_fail (path != NULL, NULL);
985 g_return_val_if_fail (path_is_group != NULL, NULL);
987 if (!gtk_tree_model_get_iter (model, &iter, path)) {
991 gtk_tree_model_get (model, &iter,
992 COL_IS_GROUP, &is_group,
996 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
1002 gtk_tree_model_get (model, &iter,
1003 COL_IS_GROUP, &is_group,
1010 *path_is_group = TRUE;
1013 gtk_tree_model_get (model, &iter,
1021 contact_list_get_group_foreach (GtkTreeModel *model,
1029 /* Groups are only at the top level. */
1030 if (gtk_tree_path_get_depth (path) != 1) {
1034 gtk_tree_model_get (model, iter,
1036 COL_IS_GROUP, &is_group,
1039 if (is_group && strcmp (str, fg->name) == 0) {
1050 contact_list_get_group (GossipContactList *list,
1052 GtkTreeIter *iter_group_to_set,
1053 GtkTreeIter *iter_separator_to_set,
1056 GossipContactListPriv *priv;
1057 GtkTreeModel *model;
1058 GtkTreeIter iter_group, iter_separator;
1061 priv = GET_PRIV (list);
1063 memset (&fg, 0, sizeof (fg));
1067 model = GTK_TREE_MODEL (priv->store);
1068 gtk_tree_model_foreach (model,
1069 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1077 gtk_tree_store_append (priv->store, &iter_group, NULL);
1078 gtk_tree_store_set (priv->store, &iter_group,
1079 COL_ICON_STATUS, NULL,
1082 COL_IS_ACTIVE, FALSE,
1083 COL_IS_SEPARATOR, FALSE,
1086 if (iter_group_to_set) {
1087 *iter_group_to_set = iter_group;
1090 gtk_tree_store_append (priv->store,
1093 gtk_tree_store_set (priv->store, &iter_separator,
1094 COL_IS_SEPARATOR, TRUE,
1097 if (iter_separator_to_set) {
1098 *iter_separator_to_set = iter_separator;
1105 if (iter_group_to_set) {
1106 *iter_group_to_set = fg.iter;
1109 iter_separator = fg.iter;
1111 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1112 gboolean is_separator;
1114 gtk_tree_model_get (model, &iter_separator,
1115 COL_IS_SEPARATOR, &is_separator,
1118 if (is_separator && iter_separator_to_set) {
1119 *iter_separator_to_set = iter_separator;
1126 contact_list_add_contact (GossipContactList *list,
1127 GossipContact *contact)
1129 GossipContactListPriv *priv;
1130 GtkTreeIter iter, iter_group, iter_separator;
1131 GtkTreeModel *model;
1134 priv = GET_PRIV (list);
1136 if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1140 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1142 /* If no groups just add it at the top level. */
1143 groups = gossip_contact_get_groups (contact);
1145 GdkPixbuf *pixbuf_avatar;
1146 gboolean show_avatar = FALSE;
1148 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1150 if (priv->show_avatars && !priv->is_compact) {
1154 gossip_debug (DEBUG_DOMAIN, "");
1155 gossip_debug (DEBUG_DOMAIN,
1156 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1158 gossip_debug (DEBUG_DOMAIN,
1159 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
1161 G_IS_OBJECT (contact) ? "yes" : "no",
1162 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1164 gtk_tree_store_append (priv->store, &iter, NULL);
1165 gtk_tree_store_set (priv->store, &iter,
1166 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1167 COL_PIXBUF_AVATAR, pixbuf_avatar,
1168 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1169 COL_NAME, gossip_contact_get_name (contact),
1170 COL_STATUS, gossip_contact_get_status (contact),
1171 COL_STATUS_VISIBLE, !priv->is_compact,
1172 COL_CONTACT, contact,
1173 COL_IS_GROUP, FALSE,
1174 COL_IS_ACTIVE, FALSE,
1175 COL_IS_ONLINE, gossip_contact_is_online (contact),
1176 COL_IS_SEPARATOR, FALSE,
1179 gossip_debug (DEBUG_DOMAIN,
1180 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1181 gossip_debug (DEBUG_DOMAIN, "");
1183 if (pixbuf_avatar) {
1184 g_object_unref (pixbuf_avatar);
1188 /* Else add to each group. */
1189 for (l = groups; l; l = l->next) {
1191 GtkTreeIter model_iter_group;
1192 GdkPixbuf *pixbuf_avatar;
1196 gboolean show_avatar = FALSE;
1203 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1205 contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
1207 if (priv->show_avatars && !priv->is_compact) {
1211 gossip_debug (DEBUG_DOMAIN, "");
1212 gossip_debug (DEBUG_DOMAIN,
1213 "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1215 gossip_debug (DEBUG_DOMAIN,
1216 "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
1218 G_IS_OBJECT (contact) ? "yes" : "no",
1219 GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1221 gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
1222 gtk_tree_store_set (priv->store, &iter,
1223 COL_ICON_STATUS, gossip_icon_name_for_contact (contact),
1224 COL_PIXBUF_AVATAR, pixbuf_avatar,
1225 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1226 COL_NAME, gossip_contact_get_name (contact),
1227 COL_STATUS, gossip_contact_get_status (contact),
1228 COL_STATUS_VISIBLE, !priv->is_compact,
1229 COL_CONTACT, contact,
1230 COL_IS_GROUP, FALSE,
1231 COL_IS_ACTIVE, FALSE,
1232 COL_IS_ONLINE, gossip_contact_is_online (contact),
1233 COL_IS_SEPARATOR, FALSE,
1236 gossip_debug (DEBUG_DOMAIN,
1237 "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above (since filter work) ^^^^^^^^^^^^^^^^");
1238 gossip_debug (DEBUG_DOMAIN, "");
1240 if (pixbuf_avatar) {
1241 g_object_unref (pixbuf_avatar);
1248 found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),
1255 path = gtk_tree_model_get_path (model, &model_iter_group);
1260 if (gossip_contact_group_get_expanded (name)) {
1261 g_signal_handlers_block_by_func (list,
1262 contact_list_row_expand_or_collapse_cb,
1263 GINT_TO_POINTER (TRUE));
1264 gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1265 g_signal_handlers_unblock_by_func (list,
1266 contact_list_row_expand_or_collapse_cb,
1267 GINT_TO_POINTER (TRUE));
1269 g_signal_handlers_block_by_func (list,
1270 contact_list_row_expand_or_collapse_cb,
1271 GINT_TO_POINTER (FALSE));
1272 gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1273 g_signal_handlers_unblock_by_func (list,
1274 contact_list_row_expand_or_collapse_cb,
1275 GINT_TO_POINTER (FALSE));
1278 gtk_tree_path_free (path);
1283 contact_list_remove_contact (GossipContactList *list,
1284 GossipContact *contact)
1286 GossipContactListPriv *priv;
1287 GtkTreeModel *model;
1290 priv = GET_PRIV (list);
1292 iters = contact_list_find_contact (list, contact);
1297 /* Clean up model */
1298 model = GTK_TREE_MODEL (priv->store);
1300 for (l = iters; l; l = l->next) {
1303 /* NOTE: it is only <= 2 here because we have
1304 * separators after the group name, otherwise it
1307 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1308 gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1309 gtk_tree_store_remove (priv->store, &parent);
1311 gtk_tree_store_remove (priv->store, l->data);
1315 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1316 g_list_free (iters);
1320 contact_list_create_model (GossipContactList *list)
1322 GossipContactListPriv *priv;
1323 GtkTreeModel *model;
1325 priv = GET_PRIV (list);
1328 g_object_unref (priv->store);
1332 g_object_unref (priv->filter);
1335 priv->store = gtk_tree_store_new (COL_COUNT,
1336 G_TYPE_STRING, /* Status icon-name */
1337 GDK_TYPE_PIXBUF, /* Avatar pixbuf */
1338 G_TYPE_BOOLEAN, /* Avatar pixbuf visible */
1339 G_TYPE_STRING, /* Name */
1340 G_TYPE_STRING, /* Status string */
1341 G_TYPE_BOOLEAN, /* Show status */
1342 GOSSIP_TYPE_CONTACT, /* Contact type */
1343 G_TYPE_BOOLEAN, /* Is group */
1344 G_TYPE_BOOLEAN, /* Is active */
1345 G_TYPE_BOOLEAN, /* Is online */
1346 G_TYPE_BOOLEAN); /* Is separator */
1348 /* Save normal model */
1349 model = GTK_TREE_MODEL (priv->store);
1351 /* Set up sorting */
1352 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1354 contact_list_name_sort_func,
1356 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1358 contact_list_state_sort_func,
1361 gossip_contact_list_set_sort_criterium (list, priv->sort_criterium);
1364 priv->filter = gtk_tree_model_filter_new (model, NULL);
1366 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
1367 (GtkTreeModelFilterVisibleFunc)
1368 contact_list_filter_func,
1371 gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
1375 contact_list_search_equal_func (GtkTreeModel *model,
1379 gpointer search_data)
1381 gchar *name, *name_folded;
1389 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1395 name_folded = g_utf8_casefold (name, -1);
1396 key_folded = g_utf8_casefold (key, -1);
1398 if (name_folded && key_folded &&
1399 strstr (name_folded, key_folded)) {
1406 g_free (name_folded);
1407 g_free (key_folded);
1413 contact_list_setup_view (GossipContactList *list)
1415 GtkCellRenderer *cell;
1416 GtkTreeViewColumn *col;
1419 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1420 contact_list_search_equal_func,
1425 "headers-visible", FALSE,
1426 "reorderable", TRUE,
1427 "show-expanders", FALSE,
1430 col = gtk_tree_view_column_new ();
1433 cell = gtk_cell_renderer_pixbuf_new ();
1434 gtk_tree_view_column_pack_start (col, cell, FALSE);
1435 gtk_tree_view_column_set_cell_data_func (
1437 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1447 cell = gossip_cell_renderer_text_new ();
1448 gtk_tree_view_column_pack_start (col, cell, TRUE);
1449 gtk_tree_view_column_set_cell_data_func (
1451 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1454 gtk_tree_view_column_add_attribute (col, cell,
1456 gtk_tree_view_column_add_attribute (col, cell,
1457 "status", COL_STATUS);
1458 gtk_tree_view_column_add_attribute (col, cell,
1459 "is_group", COL_IS_GROUP);
1462 cell = gtk_cell_renderer_pixbuf_new ();
1463 gtk_tree_view_column_pack_start (col, cell, FALSE);
1464 gtk_tree_view_column_set_cell_data_func (
1466 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1478 cell = gossip_cell_renderer_expander_new ();
1479 gtk_tree_view_column_pack_end (col, cell, FALSE);
1480 gtk_tree_view_column_set_cell_data_func (
1482 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1485 /* Actually add the column now we have added all cell renderers */
1486 gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1489 for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1490 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1494 for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1495 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1499 /* Note: We support the COPY action too, but need to make the
1500 * MOVE action the default.
1502 gtk_drag_source_set (GTK_WIDGET (list),
1505 G_N_ELEMENTS (drag_types_source),
1508 gtk_drag_dest_set (GTK_WIDGET (list),
1509 GTK_DEST_DEFAULT_ALL,
1511 G_N_ELEMENTS (drag_types_dest),
1512 GDK_ACTION_MOVE | GDK_ACTION_LINK);
1514 g_signal_connect (GTK_WIDGET (list),
1515 "drag-data-received",
1516 G_CALLBACK (contact_list_drag_data_received),
1519 /* FIXME: noticed but when you drag the row over the treeview
1520 * fast, it seems to stop redrawing itself, if we don't
1521 * connect this signal, all is fine.
1523 g_signal_connect (GTK_WIDGET (list),
1525 G_CALLBACK (contact_list_drag_motion),
1528 g_signal_connect (GTK_WIDGET (list),
1530 G_CALLBACK (contact_list_drag_begin),
1532 g_signal_connect (GTK_WIDGET (list),
1534 G_CALLBACK (contact_list_drag_data_get),
1536 g_signal_connect (GTK_WIDGET (list),
1538 G_CALLBACK (contact_list_drag_end),
1543 contact_list_drag_data_received (GtkWidget *widget,
1544 GdkDragContext *context,
1547 GtkSelectionData *selection,
1552 GossipContactListPriv *priv;
1553 GtkTreeModel *model;
1555 GtkTreeViewDropPosition position;
1556 GossipContact *contact;
1561 gboolean drag_success = TRUE;
1562 gboolean drag_del = FALSE;
1564 priv = GET_PRIV (widget);
1566 id = (const gchar*) selection->data;
1567 gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1568 context->action == GDK_ACTION_MOVE ? "move" : "",
1569 context->action == GDK_ACTION_COPY ? "copy" : "",
1572 /* FIXME: This is ambigous, an id can come from multiple accounts */
1573 contact = empathy_contact_list_find (priv->list, id);
1575 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1579 groups = gossip_contact_get_groups (contact);
1581 is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1588 if (g_list_length (groups) != 1) {
1589 /* if they have dragged a contact out of a
1590 * group then we would set the contact to have
1591 * NO groups but only if they were ONE group
1592 * to begin with - should we do this
1593 * regardless to how many groups they are in
1594 * already or not at all?
1599 gossip_contact_set_groups (contact, NULL);
1601 GList *l, *new_groups;
1605 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1606 name = contact_list_get_parent_group (model, path, &is_group);
1608 if (groups && name &&
1609 g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1614 /* Get source group information. */
1615 priv = GET_PRIV (widget);
1616 if (!priv->drag_row) {
1621 path = gtk_tree_row_reference_get_path (priv->drag_row);
1627 old_group = contact_list_get_parent_group (model, path, &is_group);
1628 gtk_tree_path_free (path);
1630 if (!name && old_group && GDK_ACTION_MOVE) {
1631 drag_success = FALSE;
1634 if (context->action == GDK_ACTION_MOVE) {
1638 /* Create new groups GList. */
1639 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1643 if (context->action == GDK_ACTION_MOVE &&
1644 old_group != NULL &&
1645 strcmp (str, old_group) == 0) {
1653 new_groups = g_list_append (new_groups, g_strdup (str));
1658 new_groups = g_list_append (new_groups, name);
1660 gossip_contact_set_groups (contact, new_groups);
1666 gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1670 contact_list_drag_motion (GtkWidget *widget,
1671 GdkDragContext *context,
1677 static DragMotionData *dm = NULL;
1680 gboolean is_different = FALSE;
1681 gboolean cleanup = TRUE;
1683 is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1694 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1695 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1700 if (!is_different && !cleanup) {
1705 gtk_tree_path_free (dm->path);
1706 if (dm->timeout_id) {
1707 g_source_remove (dm->timeout_id);
1715 if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1716 dm = g_new0 (DragMotionData, 1);
1718 dm->list = GOSSIP_CONTACT_LIST (widget);
1719 dm->path = gtk_tree_path_copy (path);
1721 dm->timeout_id = g_timeout_add (
1723 (GSourceFunc) contact_list_drag_motion_cb,
1731 contact_list_drag_motion_cb (DragMotionData *data)
1733 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1737 data->timeout_id = 0;
1743 contact_list_drag_begin (GtkWidget *widget,
1744 GdkDragContext *context,
1747 GossipContactListPriv *priv;
1748 GtkTreeSelection *selection;
1749 GtkTreeModel *model;
1753 priv = GET_PRIV (widget);
1755 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1756 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1760 path = gtk_tree_model_get_path (model, &iter);
1761 priv->drag_row = gtk_tree_row_reference_new (model, path);
1762 gtk_tree_path_free (path);
1766 contact_list_drag_data_get (GtkWidget *widget,
1767 GdkDragContext *context,
1768 GtkSelectionData *selection,
1773 GossipContactListPriv *priv;
1774 GtkTreePath *src_path;
1776 GtkTreeModel *model;
1777 GossipContact *contact;
1780 priv = GET_PRIV (widget);
1782 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1783 if (!priv->drag_row) {
1787 src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1792 if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1793 gtk_tree_path_free (src_path);
1797 gtk_tree_path_free (src_path);
1799 contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1804 id = gossip_contact_get_id (contact);
1805 g_object_unref (contact);
1808 case DND_DRAG_TYPE_CONTACT_ID:
1809 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1810 (guchar*)id, strlen (id) + 1);
1819 contact_list_drag_end (GtkWidget *widget,
1820 GdkDragContext *context,
1823 GossipContactListPriv *priv;
1825 priv = GET_PRIV (widget);
1827 if (priv->drag_row) {
1828 gtk_tree_row_reference_free (priv->drag_row);
1829 priv->drag_row = NULL;
1834 contact_list_cell_set_background (GossipContactList *list,
1835 GtkCellRenderer *cell,
1842 g_return_if_fail (list != NULL);
1843 g_return_if_fail (cell != NULL);
1845 style = gtk_widget_get_style (GTK_WIDGET (list));
1849 color = style->bg[GTK_STATE_SELECTED];
1851 /* Here we take the current theme colour and add it to
1852 * the colour for white and average the two. This
1853 * gives a colour which is inline with the theme but
1856 color.red = (color.red + (style->white).red) / 2;
1857 color.green = (color.green + (style->white).green) / 2;
1858 color.blue = (color.blue + (style->white).blue) / 2;
1861 "cell-background-gdk", &color,
1865 "cell-background-gdk", NULL,
1870 gint color_sum_normal;
1871 gint color_sum_selected;
1873 color = style->base[GTK_STATE_SELECTED];
1874 color_sum_normal = color.red+color.green+color.blue;
1875 color = style->base[GTK_STATE_NORMAL];
1876 color_sum_selected = color.red+color.green+color.blue;
1877 color = style->text_aa[GTK_STATE_INSENSITIVE];
1879 if (color_sum_normal < color_sum_selected) {
1880 /* Found a light theme */
1881 color.red = (color.red + (style->white).red) / 2;
1882 color.green = (color.green + (style->white).green) / 2;
1883 color.blue = (color.blue + (style->white).blue) / 2;
1885 /* Found a dark theme */
1886 color.red = (color.red + (style->black).red) / 2;
1887 color.green = (color.green + (style->black).green) / 2;
1888 color.blue = (color.blue + (style->black).blue) / 2;
1892 "cell-background-gdk", &color,
1899 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1900 GtkCellRenderer *cell,
1901 GtkTreeModel *model,
1903 GossipContactList *list)
1909 gtk_tree_model_get (model, iter,
1910 COL_IS_GROUP, &is_group,
1911 COL_IS_ACTIVE, &is_active,
1912 COL_ICON_STATUS, &icon_name,
1916 "visible", !is_group,
1917 "icon-name", icon_name,
1922 contact_list_cell_set_background (list, cell, is_group, is_active);
1926 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1927 GtkCellRenderer *cell,
1928 GtkTreeModel *model,
1930 GossipContactList *list)
1933 gboolean show_avatar;
1937 gtk_tree_model_get (model, iter,
1938 COL_PIXBUF_AVATAR, &pixbuf,
1939 COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1940 COL_IS_GROUP, &is_group,
1941 COL_IS_ACTIVE, &is_active,
1945 "visible", !is_group && show_avatar,
1950 g_object_unref (pixbuf);
1953 contact_list_cell_set_background (list, cell, is_group, is_active);
1957 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1958 GtkCellRenderer *cell,
1959 GtkTreeModel *model,
1961 GossipContactList *list)
1965 gboolean show_status;
1967 gtk_tree_model_get (model, iter,
1968 COL_IS_GROUP, &is_group,
1969 COL_IS_ACTIVE, &is_active,
1970 COL_STATUS_VISIBLE, &show_status,
1974 "show-status", show_status,
1977 contact_list_cell_set_background (list, cell, is_group, is_active);
1981 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1982 GtkCellRenderer *cell,
1983 GtkTreeModel *model,
1985 GossipContactList *list)
1990 gtk_tree_model_get (model, iter,
1991 COL_IS_GROUP, &is_group,
1992 COL_IS_ACTIVE, &is_active,
1995 if (gtk_tree_model_iter_has_child (model, iter)) {
1997 gboolean row_expanded;
1999 path = gtk_tree_model_get_path (model, iter);
2000 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
2001 gtk_tree_path_free (path);
2005 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
2008 g_object_set (cell, "visible", FALSE, NULL);
2011 contact_list_cell_set_background (list, cell, is_group, is_active);
2015 contact_list_get_contact_menu (GossipContactList *list,
2016 gboolean can_send_file,
2017 gboolean can_show_log)
2019 GossipContactListPriv *priv;
2023 priv = GET_PRIV (list);
2025 /* Sort out sensitive items */
2026 action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
2027 gtk_action_set_sensitive (action, can_show_log);
2029 action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
2030 gtk_action_set_visible (action, can_send_file);
2032 widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
2038 gossip_contact_list_get_group_menu (GossipContactList *list)
2040 GossipContactListPriv *priv;
2043 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2045 priv = GET_PRIV (list);
2047 widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
2053 gossip_contact_list_get_contact_menu (GossipContactList *list,
2054 GossipContact *contact)
2057 gboolean can_show_log;
2058 gboolean can_send_file;
2060 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2061 g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
2063 can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
2064 can_send_file = FALSE;
2066 menu = contact_list_get_contact_menu (list,
2073 contact_list_button_press_event_cb (GossipContactList *list,
2074 GdkEventButton *event,
2077 GossipContactListPriv *priv;
2078 GossipContact *contact;
2080 GtkTreeSelection *selection;
2081 GtkTreeModel *model;
2083 gboolean row_exists;
2086 if (event->button != 3) {
2090 priv = GET_PRIV (list);
2092 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2093 model = GTK_TREE_MODEL (priv->store);
2095 gtk_widget_grab_focus (GTK_WIDGET (list));
2097 row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
2105 gtk_tree_selection_unselect_all (selection);
2106 gtk_tree_selection_select_path (selection, path);
2108 gtk_tree_model_get_iter (model, &iter, path);
2109 gtk_tree_path_free (path);
2111 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2114 menu = gossip_contact_list_get_contact_menu (list, contact);
2115 g_object_unref (contact);
2117 menu = gossip_contact_list_get_group_menu (list);
2124 gtk_widget_show (menu);
2126 gtk_menu_popup (GTK_MENU (menu),
2127 NULL, NULL, NULL, NULL,
2128 event->button, event->time);
2134 contact_list_row_activated_cb (GossipContactList *list,
2136 GtkTreeViewColumn *col,
2139 GossipContact *contact;
2141 GtkTreeModel *model;
2144 view = GTK_TREE_VIEW (list);
2145 model = gtk_tree_view_get_model (view);
2147 gtk_tree_model_get_iter (model, &iter, path);
2148 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2151 contact_list_action_activated (list, contact);
2152 g_object_unref (contact);
2157 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2162 GtkTreeModel *model;
2166 model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2168 gtk_tree_model_get (model, iter,
2172 expanded = GPOINTER_TO_INT (user_data);
2173 gossip_contact_group_set_expanded (name, expanded);
2179 contact_list_state_sort_func (GtkTreeModel *model,
2180 GtkTreeIter *iter_a,
2181 GtkTreeIter *iter_b,
2185 gchar *name_a, *name_b;
2186 gboolean is_separator_a, is_separator_b;
2187 GossipContact *contact_a, *contact_b;
2188 GossipPresence *presence_a, *presence_b;
2189 McPresence state_a, state_b;
2191 gtk_tree_model_get (model, iter_a,
2193 COL_CONTACT, &contact_a,
2194 COL_IS_SEPARATOR, &is_separator_a,
2196 gtk_tree_model_get (model, iter_b,
2198 COL_CONTACT, &contact_b,
2199 COL_IS_SEPARATOR, &is_separator_b,
2202 /* Separator or group? */
2203 if (is_separator_a || is_separator_b) {
2204 if (is_separator_a) {
2206 } else if (is_separator_b) {
2209 } else if (!contact_a && contact_b) {
2211 } else if (contact_a && !contact_b) {
2213 } else if (!contact_a && !contact_b) {
2215 ret_val = g_utf8_collate (name_a, name_b);
2222 /* If we managed to get this far, we can start looking at
2225 presence_a = gossip_contact_get_presence (GOSSIP_CONTACT (contact_a));
2226 presence_b = gossip_contact_get_presence (GOSSIP_CONTACT (contact_b));
2228 if (!presence_a && presence_b) {
2230 } else if (presence_a && !presence_b) {
2232 } else if (!presence_a && !presence_b) {
2233 /* Both offline, sort by name */
2234 ret_val = g_utf8_collate (name_a, name_b);
2236 state_a = gossip_presence_get_state (presence_a);
2237 state_b = gossip_presence_get_state (presence_b);
2239 if (state_a < state_b) {
2241 } else if (state_a > state_b) {
2244 /* Fallback: compare by name */
2245 ret_val = g_utf8_collate (name_a, name_b);
2254 g_object_unref (contact_a);
2258 g_object_unref (contact_b);
2265 contact_list_name_sort_func (GtkTreeModel *model,
2266 GtkTreeIter *iter_a,
2267 GtkTreeIter *iter_b,
2270 gchar *name_a, *name_b;
2271 GossipContact *contact_a, *contact_b;
2272 gboolean is_separator_a, is_separator_b;
2275 gtk_tree_model_get (model, iter_a,
2277 COL_CONTACT, &contact_a,
2278 COL_IS_SEPARATOR, &is_separator_a,
2280 gtk_tree_model_get (model, iter_b,
2282 COL_CONTACT, &contact_b,
2283 COL_IS_SEPARATOR, &is_separator_b,
2286 /* If contact is NULL it means it's a group. */
2288 if (is_separator_a || is_separator_b) {
2289 if (is_separator_a) {
2291 } else if (is_separator_b) {
2294 } else if (!contact_a && contact_b) {
2296 } else if (contact_a && !contact_b) {
2299 ret_val = g_utf8_collate (name_a, name_b);
2306 g_object_unref (contact_a);
2310 g_object_unref (contact_b);
2317 contact_list_filter_show_contact (GossipContact *contact,
2318 const gchar *filter)
2323 /* Check contact id */
2324 str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
2325 visible = G_STR_EMPTY (str) || strstr (str, filter);
2332 /* Check contact name */
2333 str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
2334 visible = G_STR_EMPTY (str) || strstr (str, filter);
2341 contact_list_filter_show_group (GossipContactList *list,
2343 const gchar *filter)
2345 GossipContactListPriv *priv;
2346 GList *contacts, *l;
2348 gboolean show_group = FALSE;
2350 priv = GET_PRIV (list);
2352 str = g_utf8_casefold (group, -1);
2357 /* If the filter is the partially the group name, we show the
2360 if (strstr (str, filter)) {
2365 /* At this point, we need to check in advance if this
2366 * group should be shown because a contact we want to
2367 * show exists in it.
2369 contacts = empathy_contact_list_get_contacts (priv->list);
2370 for (l = contacts; l && !show_group; l = l->next) {
2371 if (!gossip_contact_is_in_group (l->data, group)) {
2375 if (contact_list_filter_show_contact (l->data, filter)) {
2379 g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2380 g_list_free (contacts);
2387 contact_list_filter_func (GtkTreeModel *model,
2389 GossipContactList *list)
2391 GossipContactListPriv *priv;
2393 gboolean is_separator;
2394 gboolean visible = TRUE;
2396 priv = GET_PRIV (list);
2398 if (G_STR_EMPTY (priv->filter_text)) {
2402 /* Check to see if iter matches any group names */
2403 gtk_tree_model_get (model, iter,
2404 COL_IS_GROUP, &is_group,
2405 COL_IS_SEPARATOR, &is_separator,
2411 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
2412 visible &= contact_list_filter_show_group (list,
2416 } else if (is_separator) {
2417 /* Do nothing here */
2419 GossipContact *contact;
2421 /* Check contact id */
2422 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
2423 visible &= contact_list_filter_show_contact (contact,
2425 g_object_unref (contact);
2432 contact_list_iter_equal_contact (GtkTreeModel *model,
2434 GossipContact *contact)
2439 gtk_tree_model_get (model, iter,
2447 equal = (c == contact);
2454 contact_list_find_contact_foreach (GtkTreeModel *model,
2459 if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2461 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2464 /* We want to find ALL contacts that match, this means if we
2465 * have the same contact in 3 groups, all iters should be
2472 contact_list_find_contact (GossipContactList *list,
2473 GossipContact *contact)
2475 GossipContactListPriv *priv;
2476 GtkTreeModel *model;
2480 priv = GET_PRIV (list);
2482 memset (&fc, 0, sizeof (fc));
2484 fc.contact = contact;
2486 model = GTK_TREE_MODEL (priv->store);
2487 gtk_tree_model_foreach (model,
2488 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2499 contact_list_action_cb (GtkAction *action,
2500 GossipContactList *list)
2502 GossipContact *contact;
2506 name = gtk_action_get_name (action);
2511 gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2513 contact = gossip_contact_list_get_selected (list);
2514 group = gossip_contact_list_get_selected_group (list);
2516 if (contact && strcmp (name, "Chat") == 0) {
2517 contact_list_action_activated (list, contact);
2519 else if (contact && strcmp (name, "Information") == 0) {
2521 else if (contact && strcmp (name, "Edit") == 0) {
2523 else if (contact && strcmp (name, "Remove") == 0) {
2525 else if (contact && strcmp (name, "Invite") == 0) {
2527 else if (contact && strcmp (name, "SendFile") == 0) {
2529 else if (contact && strcmp (name, "Log") == 0) {
2531 else if (group && strcmp (name, "Rename") == 0) {
2536 g_object_unref (contact);
2541 contact_list_action_activated (GossipContactList *list,
2542 GossipContact *contact)
2546 mc = gossip_mission_control_new ();
2547 mission_control_request_channel (mc,
2548 gossip_contact_get_account (contact),
2549 TP_IFACE_CHANNEL_TYPE_TEXT,
2550 gossip_contact_get_handle (contact),
2551 TP_HANDLE_TYPE_CONTACT,
2553 g_object_unref (mc);
2557 contact_list_update_list_mode_foreach (GtkTreeModel *model,
2560 GossipContactList *list)
2562 GossipContactListPriv *priv;
2563 gboolean show_avatar = FALSE;
2565 priv = GET_PRIV (list);
2567 if (priv->show_avatars && !priv->is_compact) {
2571 gtk_tree_store_set (priv->store, iter,
2572 COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2573 COL_STATUS_VISIBLE, !priv->is_compact,
2580 gossip_contact_list_new (void)
2582 return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2586 gossip_contact_list_get_selected (GossipContactList *list)
2588 GossipContactListPriv *priv;
2589 GtkTreeSelection *selection;
2591 GtkTreeModel *model;
2592 GossipContact *contact;
2594 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2596 priv = GET_PRIV (list);
2598 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2599 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2603 gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2609 gossip_contact_list_get_selected_group (GossipContactList *list)
2611 GossipContactListPriv *priv;
2612 GtkTreeSelection *selection;
2614 GtkTreeModel *model;
2618 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2620 priv = GET_PRIV (list);
2622 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2623 if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2627 gtk_tree_model_get (model, &iter,
2628 COL_IS_GROUP, &is_group,
2641 gossip_contact_list_get_show_offline (GossipContactList *list)
2643 GossipContactListPriv *priv;
2645 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2647 priv = GET_PRIV (list);
2649 return priv->show_offline;
2653 gossip_contact_list_get_show_avatars (GossipContactList *list)
2655 GossipContactListPriv *priv;
2657 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2659 priv = GET_PRIV (list);
2661 return priv->show_avatars;
2665 gossip_contact_list_get_is_compact (GossipContactList *list)
2667 GossipContactListPriv *priv;
2669 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2671 priv = GET_PRIV (list);
2673 return priv->is_compact;
2676 GossipContactListSort
2677 gossip_contact_list_get_sort_criterium (GossipContactList *list)
2679 GossipContactListPriv *priv;
2681 g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), 0);
2683 priv = GET_PRIV (list);
2685 return priv->sort_criterium;
2689 gossip_contact_list_set_show_offline (GossipContactList *list,
2690 gboolean show_offline)
2692 GossipContactListPriv *priv;
2693 GList *contacts, *l;
2694 gboolean show_active;
2696 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2698 priv = GET_PRIV (list);
2700 priv->show_offline = show_offline;
2701 show_active = priv->show_active;
2703 /* Disable temporarily. */
2704 priv->show_active = FALSE;
2706 contacts = empathy_contact_list_get_contacts (priv->list);
2707 for (l = contacts; l; l = l->next) {
2708 GossipContact *contact;
2710 contact = GOSSIP_CONTACT (l->data);
2712 contact_list_contact_update (list, contact);
2714 g_object_unref (contact);
2716 g_list_free (contacts);
2718 /* Restore to original setting. */
2719 priv->show_active = show_active;
2723 gossip_contact_list_set_show_avatars (GossipContactList *list,
2724 gboolean show_avatars)
2726 GossipContactListPriv *priv;
2727 GtkTreeModel *model;
2729 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2731 priv = GET_PRIV (list);
2733 priv->show_avatars = show_avatars;
2735 model = GTK_TREE_MODEL (priv->store);
2737 gtk_tree_model_foreach (model,
2738 (GtkTreeModelForeachFunc)
2739 contact_list_update_list_mode_foreach,
2744 gossip_contact_list_set_is_compact (GossipContactList *list,
2745 gboolean is_compact)
2747 GossipContactListPriv *priv;
2748 GtkTreeModel *model;
2750 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2752 priv = GET_PRIV (list);
2754 priv->is_compact = is_compact;
2756 model = GTK_TREE_MODEL (priv->store);
2758 gtk_tree_model_foreach (model,
2759 (GtkTreeModelForeachFunc)
2760 contact_list_update_list_mode_foreach,
2765 gossip_contact_list_set_sort_criterium (GossipContactList *list,
2766 GossipContactListSort sort_criterium)
2768 GossipContactListPriv *priv;
2770 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2772 priv = GET_PRIV (list);
2774 priv->sort_criterium = sort_criterium;
2776 switch (sort_criterium) {
2777 case GOSSIP_CONTACT_LIST_SORT_STATE:
2778 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2780 GTK_SORT_ASCENDING);
2783 case GOSSIP_CONTACT_LIST_SORT_NAME:
2784 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (priv->store),
2786 GTK_SORT_ASCENDING);
2792 gossip_contact_list_set_filter (GossipContactList *list,
2793 const gchar *filter)
2795 GossipContactListPriv *priv;
2797 g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2799 priv = GET_PRIV (list);
2801 g_free (priv->filter_text);
2803 priv->filter_text = g_utf8_casefold (filter, -1);
2805 priv->filter_text = NULL;
2808 gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
2809 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));