3a49d12d0bfed0813f2164c8377577e0dabd93e9
[empathy.git] / libempathy-gtk / gossip-contact-list.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  *
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.
9  *
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.
14  *
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.
19  *
20  * Authors: Mikael Hallendal <micke@imendio.com>
21  *          Martyn Russell <martyn@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27
28 #include <glib/gi18n.h>
29 #include <gtk/gtk.h>
30 #include <glade/glade.h>
31
32 #include <libmissioncontrol/mc-account.h>
33
34 #include <libempathy/empathy-contact-manager.h>
35 #include <libempathy/gossip-debug.h>
36 #include <libempathy/empathy-session.h>
37
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"
49
50 #define DEBUG_DOMAIN "ContactListUI"
51
52 /* Flashing delay for icons (milliseconds). */
53 #define FLASH_TIMEOUT 500
54
55 /* Active users are those which have recently changed state
56  * (e.g. online, offline or from normal to a busy state).
57  */
58
59 /* Time user is shown as active */
60 #define ACTIVE_USER_SHOW_TIME 7000
61
62 /* Time after connecting which we wait before active users are enabled */
63 #define ACTIVE_USER_WAIT_TO_ENABLE_TIME 5000
64
65 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST, GossipContactListPriv))
66
67 struct _GossipContactListPriv {
68         EmpathyContactManager *manager;
69
70         GHashTable            *groups;
71
72         GtkUIManager          *ui;
73         GtkTreeRowReference   *drag_row;
74
75         gboolean               show_offline;
76         gboolean               show_avatars;
77         gboolean               is_compact;
78         gboolean               show_active;
79 };
80
81 typedef struct {
82         const gchar *name;
83         gboolean     found;
84         GtkTreeIter  iter;
85 } FindGroup;
86
87 typedef struct {
88         GossipContact *contact;
89         gboolean       found;
90         GList         *iters;
91 } FindContact;
92
93 typedef struct {
94         GossipContactList *list;
95         GtkTreePath       *path;
96         guint              timeout_id;
97 } DragMotionData;
98
99 typedef struct {
100         GossipContactList *list;
101         GossipContact     *contact;
102         gboolean           remove;
103 } ShowActiveData;
104
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,
109                                                               guint                   param_id,
110                                                               GValue                 *value,
111                                                               GParamSpec             *pspec);
112 static void     contact_list_set_property                    (GObject                *object,
113                                                               guint                   param_id,
114                                                               const GValue           *value,
115                                                               GParamSpec             *pspec);
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,
122                                                               GParamSpec             *param,
123                                                               GossipContactList      *list);
124 static void     contact_list_contact_groups_updated_cb       (GossipContact          *contact,
125                                                               GParamSpec             *param,
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,
132                                                               gboolean                active,
133                                                               gboolean                set_changed);
134 static ShowActiveData *
135                 contact_list_contact_active_new              (GossipContactList      *list,
136                                                               GossipContact          *contact,
137                                                               gboolean                remove);
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,
141                                                               GtkTreePath            *path,
142                                                               gboolean               *path_is_group);
143 static void     contact_list_get_group                       (GossipContactList      *list,
144                                                               const gchar            *name,
145                                                               GtkTreeIter            *iter_to_set,
146                                                               gboolean               *created);
147 static gboolean contact_list_get_group_foreach               (GtkTreeModel           *model,
148                                                               GtkTreePath            *path,
149                                                               GtkTreeIter            *iter,
150                                                               FindGroup              *fg);
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,
157                                                               gint                    column,
158                                                               const gchar            *key,
159                                                               GtkTreeIter            *iter,
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,
164                                                               gint                    x,
165                                                               gint                    y,
166                                                               GtkSelectionData       *selection,
167                                                               guint                   info,
168                                                               guint                   time,
169                                                               gpointer                user_data);
170 static gboolean contact_list_drag_motion                     (GtkWidget              *widget,
171                                                               GdkDragContext         *context,
172                                                               gint                    x,
173                                                               gint                    y,
174                                                               guint                   time,
175                                                               gpointer                data);
176 static gboolean contact_list_drag_motion_cb                  (DragMotionData         *data);
177 static void     contact_list_drag_begin                      (GtkWidget              *widget,
178                                                               GdkDragContext         *context,
179                                                               gpointer                user_data);
180 static void     contact_list_drag_data_get                   (GtkWidget              *widget,
181                                                               GdkDragContext         *contact,
182                                                               GtkSelectionData       *selection,
183                                                               guint                   info,
184                                                               guint                   time,
185                                                               gpointer                user_data);
186 static void     contact_list_drag_end                        (GtkWidget              *widget,
187                                                               GdkDragContext         *context,
188                                                               gpointer                user_data);
189 static void     contact_list_cell_set_background             (GossipContactList      *list,
190                                                               GtkCellRenderer        *cell,
191                                                               gboolean                is_group,
192                                                               gboolean                is_active);
193 static void     contact_list_pixbuf_cell_data_func           (GtkTreeViewColumn      *tree_column,
194                                                               GtkCellRenderer        *cell,
195                                                               GtkTreeModel           *model,
196                                                               GtkTreeIter            *iter,
197                                                               GossipContactList      *list);
198 static void     contact_list_avatar_cell_data_func           (GtkTreeViewColumn      *tree_column,
199                                                               GtkCellRenderer        *cell,
200                                                               GtkTreeModel           *model,
201                                                               GtkTreeIter            *iter,
202                                                               GossipContactList      *list);
203 static void     contact_list_text_cell_data_func             (GtkTreeViewColumn      *tree_column,
204                                                               GtkCellRenderer        *cell,
205                                                               GtkTreeModel           *model,
206                                                               GtkTreeIter            *iter,
207                                                               GossipContactList      *list);
208 static void     contact_list_expander_cell_data_func         (GtkTreeViewColumn      *column,
209                                                               GtkCellRenderer        *cell,
210                                                               GtkTreeModel           *model,
211                                                               GtkTreeIter            *iter,
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,
218                                                               gpointer                user_data);
219 static void     contact_list_row_activated_cb                (GossipContactList      *list,
220                                                               GtkTreePath            *path,
221                                                               GtkTreeViewColumn      *col,
222                                                               gpointer                user_data);
223 static void     contact_list_row_expand_or_collapse_cb       (GossipContactList      *list,
224                                                               GtkTreeIter            *iter,
225                                                               GtkTreePath            *path,
226                                                               gpointer                user_data);
227 static gint     contact_list_sort_func                       (GtkTreeModel           *model,
228                                                               GtkTreeIter            *iter_a,
229                                                               GtkTreeIter            *iter_b,
230                                                               gpointer                user_data);
231 static GList *  contact_list_find_contact                    (GossipContactList      *list,
232                                                               GossipContact          *contact);
233 static gboolean contact_list_find_contact_foreach            (GtkTreeModel           *model,
234                                                               GtkTreePath            *path,
235                                                               GtkTreeIter            *iter,
236                                                               FindContact            *fc);
237 static void     contact_list_action_cb                       (GtkAction              *action,
238                                                               GossipContactList      *list);
239 static gboolean contact_list_update_list_mode_foreach        (GtkTreeModel           *model,
240                                                               GtkTreePath            *path,
241                                                               GtkTreeIter            *iter,
242                                                               GossipContactList      *list);
243 enum {
244         CONTACT_CHAT,
245         CONTACT_INFORMATION,
246         CONTACT_EDIT,
247         CONTACT_REMOVE,
248         CONTACT_INVITE,
249         CONTACT_SEND_FILE,
250         CONTACT_LOG,
251         GROUP_RENAME,
252         LAST_SIGNAL
253 };
254
255 static guint signals[LAST_SIGNAL];
256
257 enum {
258         COL_PIXBUF_STATUS,
259         COL_PIXBUF_AVATAR,
260         COL_PIXBUF_AVATAR_VISIBLE,
261         COL_NAME,
262         COL_STATUS,
263         COL_STATUS_VISIBLE,
264         COL_CONTACT,
265         COL_IS_GROUP,
266         COL_IS_ACTIVE,
267         COL_IS_ONLINE,
268         COL_COUNT
269 };
270
271 enum {
272         PROP_0,
273         PROP_SHOW_OFFLINE,
274         PROP_SHOW_AVATARS,
275         PROP_IS_COMPACT,
276 };
277
278 static const GtkActionEntry entries[] = {
279         { "ContactMenu", NULL,
280           N_("_Contact"), NULL, NULL,
281           NULL
282         },
283         { "GroupMenu", NULL,
284           N_("_Group"),NULL, NULL,
285           NULL
286         },
287         { "Chat", GOSSIP_STOCK_MESSAGE,
288           N_("_Chat"), NULL, N_("Chat with contact"),
289           G_CALLBACK (contact_list_action_cb)
290         },
291         { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
292           N_("Infor_mation"), "<control>I", N_("View contact information"),
293           G_CALLBACK (contact_list_action_cb)
294         },
295         { "Rename", NULL,
296           N_("Re_name"), NULL, N_("Rename"),
297           G_CALLBACK (contact_list_action_cb)
298         },
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)
302         },
303         { "Remove", GTK_STOCK_REMOVE,
304           N_("_Remove"), NULL, N_("Remove contact"),
305           G_CALLBACK (contact_list_action_cb)
306         },
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)
310         },
311         { "SendFile", NULL,
312           N_("_Send File..."), NULL, N_("Send a file"),
313           G_CALLBACK (contact_list_action_cb)
314         },
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)
318         },
319 };
320
321 static guint n_entries = G_N_ELEMENTS (entries);
322
323 static const gchar *ui_info =
324         "<ui>"
325         "  <popup name='Contact'>"
326         "    <menuitem action='Chat'/>"
327         "    <menuitem action='Log'/>"
328         "    <menuitem action='SendFile'/>"
329         "    <separator/>"
330         "    <menuitem action='Invite'/>"
331         "    <separator/>"
332         "    <menuitem action='Edit'/>"
333         "    <menuitem action='Remove'/>"
334         "    <separator/>"
335         "    <menuitem action='Information'/>"
336         "  </popup>"
337         "  <popup name='Group'>"
338         "    <menuitem action='Rename'/>"
339         "  </popup>"
340         "</ui>";
341
342 enum DndDragType {
343         DND_DRAG_TYPE_CONTACT_ID,
344         DND_DRAG_TYPE_URL,
345         DND_DRAG_TYPE_STRING,
346 };
347
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 },
353 };
354
355 static const GtkTargetEntry drag_types_source[] = {
356         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
357 };
358
359 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
360 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
361
362 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
363
364 static void
365 gossip_contact_list_class_init (GossipContactListClass *klass)
366 {
367         GObjectClass *object_class = G_OBJECT_CLASS (klass);
368
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;
372
373         signals[CONTACT_CHAT] =
374                 g_signal_new ("contact-chat",
375                               G_TYPE_FROM_CLASS (klass),
376                               G_SIGNAL_RUN_LAST,
377                               0,
378                               NULL, NULL,
379                               g_cclosure_marshal_VOID__OBJECT,
380                               G_TYPE_NONE,
381                               1, GOSSIP_TYPE_CONTACT);
382         signals[CONTACT_INFORMATION] =
383                 g_signal_new ("contact-information",
384                               G_TYPE_FROM_CLASS (klass),
385                               G_SIGNAL_RUN_LAST,
386                               0,
387                               NULL, NULL,
388                               g_cclosure_marshal_VOID__OBJECT,
389                               G_TYPE_NONE,
390                               1, GOSSIP_TYPE_CONTACT);
391         signals[CONTACT_EDIT] =
392                 g_signal_new ("contact-edit",
393                               G_TYPE_FROM_CLASS (klass),
394                               G_SIGNAL_RUN_LAST,
395                               0,
396                               NULL, NULL,
397                               g_cclosure_marshal_VOID__OBJECT,
398                               G_TYPE_NONE,
399                               1, GOSSIP_TYPE_CONTACT);
400         signals[CONTACT_REMOVE] =
401                 g_signal_new ("contact-remove",
402                               G_TYPE_FROM_CLASS (klass),
403                               G_SIGNAL_RUN_LAST,
404                               0,
405                               NULL, NULL,
406                               g_cclosure_marshal_VOID__OBJECT,
407                               G_TYPE_NONE,
408                               1, GOSSIP_TYPE_CONTACT);
409         signals[CONTACT_INVITE] =
410                 g_signal_new ("contact-invite",
411                               G_TYPE_FROM_CLASS (klass),
412                               G_SIGNAL_RUN_LAST,
413                               0,
414                               NULL, NULL,
415                               g_cclosure_marshal_VOID__OBJECT,
416                               G_TYPE_NONE,
417                               1, GOSSIP_TYPE_CONTACT);
418         signals[CONTACT_SEND_FILE] =
419                 g_signal_new ("contact-send-file",
420                               G_TYPE_FROM_CLASS (klass),
421                               G_SIGNAL_RUN_LAST,
422                               0,
423                               NULL, NULL,
424                               g_cclosure_marshal_VOID__OBJECT,
425                               G_TYPE_NONE,
426                               1, GOSSIP_TYPE_CONTACT);
427         signals[CONTACT_LOG] =
428                 g_signal_new ("contact-log",
429                               G_TYPE_FROM_CLASS (klass),
430                               G_SIGNAL_RUN_LAST,
431                               0,
432                               NULL, NULL,
433                               g_cclosure_marshal_VOID__OBJECT,
434                               G_TYPE_NONE,
435                               1, GOSSIP_TYPE_CONTACT);
436         signals[GROUP_RENAME] =
437                 g_signal_new ("group-rename",
438                               G_TYPE_FROM_CLASS (klass),
439                               G_SIGNAL_RUN_LAST,
440                               0,
441                               NULL, NULL,
442                               g_cclosure_marshal_VOID__STRING,
443                               G_TYPE_NONE,
444                               1, G_TYPE_STRING);
445
446
447         g_object_class_install_property (object_class,
448                                          PROP_SHOW_OFFLINE,
449                                          g_param_spec_boolean ("show-offline",
450                                                                "Show Offline",
451                                                                "Whether contact list should display "
452                                                                "offline contacts",
453                                                                FALSE,
454                                                                G_PARAM_READWRITE));
455          g_object_class_install_property (object_class,
456                                           PROP_SHOW_AVATARS,
457                                           g_param_spec_boolean ("show-avatars",
458                                                                 "Show Avatars",
459                                                                 "Whether contact list should display "
460                                                                 "avatars for contacts",
461                                                                 TRUE,
462                                                                 G_PARAM_READWRITE));
463         g_object_class_install_property (object_class,
464                                          PROP_IS_COMPACT,
465                                          g_param_spec_boolean ("is-compact",
466                                                                "Is Compact",
467                                                                "Whether the contact list is in compact mode or not",
468                                                                FALSE,
469                                                                G_PARAM_READWRITE));
470
471         g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
472 }
473
474 static void
475 gossip_contact_list_init (GossipContactList *list)
476 {
477         GossipContactListPriv *priv;
478         GtkActionGroup        *action_group;
479         GList                 *contacts, *l;
480         GError                *error = NULL;
481
482         priv = GET_PRIV (list);
483
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;
489
490         contact_list_create_model (list);
491         contact_list_setup_view (list);
492         empathy_contact_manager_setup (priv->manager);
493
494         /* Get saved group states. */
495         gossip_contact_groups_get_all ();
496
497         /* Set up UI Manager */
498         priv->ui = gtk_ui_manager_new ();
499
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);
504
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);
508         }
509
510         g_object_unref (action_group);
511
512         /* Signal connection. */
513         g_signal_connect (priv->manager,
514                           "contact-added",
515                           G_CALLBACK (contact_list_contact_added_cb),
516                           list);
517         g_signal_connect (priv->manager,
518                           "contact-removed",
519                           G_CALLBACK (contact_list_contact_removed_cb),
520                           list);
521
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),
526                           NULL);
527         g_signal_connect (list,
528                           "row-activated",
529                           G_CALLBACK (contact_list_row_activated_cb),
530                           NULL);
531         g_signal_connect (list,
532                           "row-expanded",
533                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
534                           GINT_TO_POINTER (TRUE));
535         g_signal_connect (list,
536                           "row-collapsed",
537                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
538                           GINT_TO_POINTER (FALSE));
539
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;
544
545                 contact = l->data;
546
547                 contact_list_contact_added_cb (priv->manager, contact, list);
548
549                 g_object_unref (contact);
550         }
551 }
552
553 static void
554 contact_list_finalize (GObject *object)
555 {
556         GossipContactListPriv *priv;
557
558         priv = GET_PRIV (object);
559
560         /* FIXME: disconnect all signals on the manager and contacts */
561
562         g_object_unref (priv->manager);
563         g_object_unref (priv->ui);
564
565         G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
566 }
567
568 static void
569 contact_list_get_property (GObject    *object,
570                            guint       param_id,
571                            GValue     *value,
572                            GParamSpec *pspec)
573 {
574         GossipContactListPriv *priv;
575
576         priv = GET_PRIV (object);
577
578         switch (param_id) {
579         case PROP_SHOW_OFFLINE:
580                 g_value_set_boolean (value, priv->show_offline);
581                 break;
582         case PROP_SHOW_AVATARS:
583                 g_value_set_boolean (value, priv->show_avatars);
584                 break;
585         case PROP_IS_COMPACT:
586                 g_value_set_boolean (value, priv->is_compact);
587                 break;
588         default:
589                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
590                 break;
591         };
592 }
593
594 static void
595 contact_list_set_property (GObject      *object,
596                            guint         param_id,
597                            const GValue *value,
598                            GParamSpec   *pspec)
599 {
600         GossipContactListPriv *priv;
601
602         priv = GET_PRIV (object);
603
604         switch (param_id) {
605         case PROP_SHOW_OFFLINE:
606                 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
607                                                       g_value_get_boolean (value));
608                 break;
609         case PROP_SHOW_AVATARS:
610                 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
611                                                       g_value_get_boolean (value));
612                 break;
613         case PROP_IS_COMPACT:
614                 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
615                                                     g_value_get_boolean (value));
616                 break;
617         default:
618                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
619                 break;
620         };
621 }
622
623 static void
624 contact_list_contact_update (GossipContactList *list,
625                              GossipContact     *contact)
626 {
627         GossipContactListPriv *priv;
628         ShowActiveData        *data;
629         GtkTreeModel          *model;
630         GList                 *iters, *l;
631         gboolean               in_list;
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;
641
642         priv = GET_PRIV (list);
643
644         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
645
646         iters = contact_list_find_contact (list, contact);
647         if (!iters) {
648                 in_list = FALSE;
649         } else {
650                 in_list = TRUE;
651         }
652
653         /* Get online state now. */
654         now_online = gossip_contact_is_online (contact);
655
656         if (priv->show_offline || now_online) {
657                 should_be_in_list = TRUE;
658         } else {
659                 should_be_in_list = FALSE;
660         }
661
662         if (!in_list && !should_be_in_list) {
663                 /* Nothing to do. */
664                 gossip_debug (DEBUG_DOMAIN,
665                               "Contact:'%s' in list:NO, should be:NO",
666                               gossip_contact_get_name (contact));
667
668                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
669                 g_list_free (iters);
670                 return;
671         }
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));
676
677                 if (priv->show_active) {
678                         do_remove = TRUE;
679                         do_set_active = TRUE;
680                         do_set_refresh = TRUE;
681
682                         set_model = TRUE;
683                         gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
684                 } else {
685                         gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
686                         contact_list_remove_contact (list, contact);
687                 }
688         }
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));
693
694                 contact_list_add_contact (list, contact);
695
696                 if (priv->show_active) {
697                         do_set_active = TRUE;
698
699                         gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
700                 }
701         } else {
702                 gossip_debug (DEBUG_DOMAIN,
703                               "Contact:'%s' in list:YES, should be:YES",
704                               gossip_contact_get_name (contact));
705
706                 /* Get online state before. */
707                 if (iters && g_list_length (iters) > 0) {
708                         GtkTreeIter *iter;
709
710                         iter = g_list_nth_data (iters, 0);
711                         gtk_tree_model_get (model, iter, COL_IS_ONLINE, &was_online, -1);
712                 }
713
714                 /* Is this really an update or an online/offline. */
715                 if (priv->show_active) {
716                         if (was_online != now_online) {
717                                 gchar *str;
718
719                                 do_set_active = TRUE;
720                                 do_set_refresh = TRUE;
721
722                                 if (was_online) {
723                                         str = "online  -> offline";
724                                 } else {
725                                         str = "offline -> online";
726                                 }
727
728                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
729                         } else {
730                                 /* Was TRUE for presence updates. */
731                                 /* do_set_active = FALSE;  */
732                                 do_set_refresh = TRUE;
733
734                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
735                         }
736                 }
737
738                 set_model = TRUE;
739         }
740
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,
750                                     -1);
751         }
752
753         if (pixbuf_presence) {
754                 g_object_unref (pixbuf_presence);
755         }
756         if (pixbuf_avatar) {
757                 g_object_unref (pixbuf_avatar);
758         }
759
760         if (priv->show_active && do_set_active) {
761                 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
762
763                 if (do_set_active) {
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,
767                                        data);
768                 }
769         }
770
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.
775          */
776         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
777         g_list_free (iters);
778 }
779
780 static void
781 contact_list_contact_added_cb (EmpathyContactManager *manager,
782                                GossipContact         *contact,
783                                GossipContactList     *list)
784 {
785         GossipContactListPriv *priv;
786
787         priv = GET_PRIV (list);
788
789         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' added",
790                       gossip_contact_get_name (contact));
791
792         /* Connect notifications for contact updates */
793         g_signal_connect (contact, "notify::groups",
794                           G_CALLBACK (contact_list_contact_groups_updated_cb),
795                           list);
796         g_signal_connect (contact, "notify::presence",
797                           G_CALLBACK (contact_list_contact_updated_cb),
798                           list);
799         g_signal_connect (contact, "notify::name",
800                           G_CALLBACK (contact_list_contact_updated_cb),
801                           list);
802         g_signal_connect (contact, "notify::avatar",
803                           G_CALLBACK (contact_list_contact_updated_cb),
804                           list);
805         g_signal_connect (contact, "notify::type",
806                           G_CALLBACK (contact_list_contact_updated_cb),
807                           list);
808
809         contact_list_add_contact (list, contact);
810 }
811
812 static void
813 contact_list_contact_groups_updated_cb (GossipContact     *contact,
814                                         GParamSpec        *param,
815                                         GossipContactList *list)
816 {
817         GossipContactListPriv *priv;
818
819         priv = GET_PRIV (list);
820
821         if (priv->show_offline || gossip_contact_is_online (contact)) {
822         }
823
824
825         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
826                       gossip_contact_get_name (contact));
827
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.
831          */
832         contact_list_remove_contact (list, contact);
833         contact_list_add_contact (list, contact);
834 }
835
836 static void
837 contact_list_contact_updated_cb (GossipContact     *contact,
838                                  GParamSpec        *param,
839                                  GossipContactList *list)
840 {
841         contact_list_contact_update (list, contact);
842 }
843
844 static void
845 contact_list_contact_removed_cb (EmpathyContactManager *manager,
846                                  GossipContact         *contact,
847                                  GossipContactList     *list)
848 {
849         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
850                       gossip_contact_get_name (contact));
851
852         /* Disconnect signals */
853         g_signal_handlers_disconnect_by_func (contact, 
854                                               G_CALLBACK (contact_list_contact_groups_updated_cb),
855                                               list);
856         g_signal_handlers_disconnect_by_func (contact,
857                                               G_CALLBACK (contact_list_contact_updated_cb),
858                                               list);
859
860         contact_list_remove_contact (list, contact);
861 }
862
863 static void
864 contact_list_contact_set_active (GossipContactList *list,
865                                  GossipContact     *contact,
866                                  gboolean           active,
867                                  gboolean           set_changed)
868 {
869         GtkTreeModel *model;
870         GList        *iters, *l;
871
872         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
873
874         iters = contact_list_find_contact (list, contact);
875         for (l = iters; l; l = l->next) {
876                 GtkTreePath *path;
877                 GtkTreeIter *iter;
878
879                 iter = l->data;
880
881                 gtk_tree_store_set (GTK_TREE_STORE (model), iter,
882                                     COL_IS_ACTIVE, active,
883                                     -1);
884                 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
885
886                 if (set_changed) {
887                         path = gtk_tree_model_get_path (model, iter);
888                         gtk_tree_model_row_changed (model, path, iter);
889                         gtk_tree_path_free (path);
890                 }
891         }
892
893         g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
894         g_list_free (iters);
895 }
896
897 static ShowActiveData *
898 contact_list_contact_active_new (GossipContactList *list,
899                                  GossipContact     *contact,
900                                  gboolean           remove)
901 {
902         ShowActiveData *data;
903
904         g_return_val_if_fail (list != NULL, NULL);
905         g_return_val_if_fail (contact != NULL, NULL);
906
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");
911         
912         data = g_slice_new0 (ShowActiveData);
913
914         data->list = g_object_ref (list);
915         data->contact = g_object_ref (contact);
916
917         data->remove = remove;
918
919         return data;
920 }
921
922 static void
923 contact_list_contact_active_free (ShowActiveData *data)
924 {
925         g_return_if_fail (data != NULL);
926
927         g_object_unref (data->contact);
928         g_object_unref (data->list);
929
930         g_slice_free (ShowActiveData, data);
931 }
932
933 static gboolean
934 contact_list_contact_active_cb (ShowActiveData *data)
935 {
936         GossipContactListPriv *priv;
937
938         g_return_val_if_fail (data != NULL, FALSE);
939
940         priv = GET_PRIV (data->list);
941
942         if (data->remove &&
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,
949                                              data->contact);
950         }
951
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,
956                                          data->contact,
957                                          FALSE,
958                                          TRUE);
959
960         contact_list_contact_active_free (data);
961
962         return FALSE;
963 }
964
965 static gchar *
966 contact_list_get_parent_group (GtkTreeModel *model,
967                                GtkTreePath  *path,
968                                gboolean     *path_is_group)
969 {
970         GtkTreeIter  parent_iter, iter;
971         gchar       *name;
972         gboolean     is_group;
973
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);
977
978         if (!gtk_tree_model_get_iter (model, &iter, path)) {
979                 return NULL;
980         }
981
982         gtk_tree_model_get (model, &iter,
983                             COL_IS_GROUP, &is_group,
984                             -1);
985
986         if (!is_group) {
987                 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
988                         return NULL;
989                 }
990
991                 iter = parent_iter;
992
993                 gtk_tree_model_get (model, &iter,
994                                     COL_IS_GROUP, &is_group,
995                                     -1);
996
997                 if (!is_group) {
998                         return NULL;
999                 }
1000
1001                 *path_is_group = TRUE;
1002         }
1003
1004         gtk_tree_model_get (model, &iter,
1005                             COL_NAME, &name,
1006                             -1);
1007
1008         return name;
1009 }
1010
1011 static void
1012 contact_list_get_group (GossipContactList *list,
1013                         const gchar       *name,
1014                         GtkTreeIter       *iter_to_set,
1015                         gboolean          *created)
1016 {
1017         GtkTreeModel *model;
1018         FindGroup     fg;
1019
1020         memset (&fg, 0, sizeof (fg));
1021
1022         fg.name = name;
1023
1024         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1025         gtk_tree_model_foreach (model,
1026                                 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1027                                 &fg);
1028
1029         if (!fg.found) {
1030                 if (created) {
1031                         *created = TRUE;
1032                 }
1033
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,
1037                                     COL_NAME, name,
1038                                     COL_IS_GROUP, TRUE,
1039                                     COL_IS_ACTIVE, FALSE,
1040                                     -1);
1041         } else {
1042                 if (created) {
1043                         *created = FALSE;
1044                 }
1045
1046                 *iter_to_set = fg.iter;
1047         }
1048 }
1049
1050 static gboolean
1051 contact_list_get_group_foreach (GtkTreeModel *model,
1052                                 GtkTreePath  *path,
1053                                 GtkTreeIter  *iter,
1054                                 FindGroup    *fg)
1055 {
1056         gchar    *str;
1057         gboolean  is_group;
1058
1059         /* Groups are only at the top level. */
1060         if (gtk_tree_path_get_depth (path) != 1) {
1061                 return FALSE;
1062         }
1063
1064         gtk_tree_model_get (model, iter,
1065                             COL_NAME, &str,
1066                             COL_IS_GROUP, &is_group,
1067                             -1);
1068         if (is_group && strcmp (str, fg->name) == 0) {
1069                 fg->found = TRUE;
1070                 fg->iter = *iter;
1071         }
1072
1073         g_free (str);
1074
1075         return fg->found;
1076 }
1077
1078 static void
1079 contact_list_add_contact (GossipContactList *list,
1080                           GossipContact     *contact)
1081 {
1082         GossipContactListPriv *priv;
1083         GtkTreeIter            iter, iter_group;
1084         GtkTreeModel          *model;
1085         GList                 *l, *groups;
1086
1087         priv = GET_PRIV (list);
1088         
1089         if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1090                 return;
1091         }
1092
1093         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1094
1095         /* If no groups just add it at the top level. */
1096         groups = gossip_contact_get_groups (contact);
1097         if (!groups) {
1098                 GdkPixbuf *pixbuf_status;
1099                 GdkPixbuf *pixbuf_avatar;
1100                 gboolean   show_avatar = FALSE;
1101
1102                 pixbuf_status = gossip_pixbuf_for_contact (contact);
1103                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
1104                         contact, 32, 32);
1105
1106                 if (priv->show_avatars && !priv->is_compact) {
1107                         show_avatar = TRUE;
1108                 }
1109
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),
1122                                     -1);
1123
1124                 if (pixbuf_avatar) {
1125                         g_object_unref (pixbuf_avatar);
1126                 }
1127                 if (pixbuf_status) {
1128                         g_object_unref (pixbuf_status);
1129                 }
1130         }
1131
1132         /* Else add to each group. */
1133         for (l = groups; l; l = l->next) {
1134                 GtkTreePath *path;
1135                 GdkPixbuf   *pixbuf_status;
1136                 GdkPixbuf   *pixbuf_avatar;
1137                 const gchar *name;
1138                 gboolean     created;
1139                 gboolean     show_avatar = FALSE;
1140
1141                 name = l->data;
1142                 if (!name) {
1143                         continue;
1144                 }
1145
1146                 pixbuf_status = gossip_pixbuf_for_contact (contact);
1147                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (
1148                         contact, 32, 32);
1149
1150                 contact_list_get_group (list, name, &iter_group, &created);
1151
1152                 if (priv->show_avatars && !priv->is_compact) {
1153                         show_avatar = TRUE;
1154                 }
1155
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),
1168                                     -1);
1169
1170                 if (pixbuf_avatar) {
1171                         g_object_unref (pixbuf_avatar);
1172                 }
1173                 if (pixbuf_status) {
1174                         g_object_unref (pixbuf_status);
1175                 }
1176
1177                 if (!created) {
1178                         continue;
1179                 }
1180
1181                 path = gtk_tree_model_get_path (model, &iter_group);
1182                 if (!path) {
1183                         continue;
1184                 }
1185
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));
1194                 } else {
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));
1202                 }
1203
1204                 gtk_tree_path_free (path);
1205         }
1206 }
1207
1208 static void
1209 contact_list_remove_contact (GossipContactList *list,
1210                              GossipContact     *contact)
1211 {
1212         GossipContactListPriv *priv;
1213         GtkTreeModel          *model;
1214         GList                 *iters, *l;
1215
1216         priv = GET_PRIV (list);
1217
1218         iters = contact_list_find_contact (list, contact);
1219         if (!iters) {
1220                 return;
1221         }
1222         
1223         /* Clean up model */
1224         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1225
1226         for (l = iters; l; l = l->next) {
1227                 GtkTreeIter parent;
1228
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);
1232                 } else {
1233                         gtk_tree_store_remove (GTK_TREE_STORE (model), l->data);
1234                 }
1235         }
1236
1237         g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
1238         g_list_free (iters);
1239 }
1240
1241 static void
1242 contact_list_create_model (GossipContactList *list)
1243 {
1244         GtkTreeModel *model;
1245
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 */
1258
1259         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1260                                          COL_NAME,
1261                                          contact_list_sort_func,
1262                                          list, NULL);
1263
1264         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
1265                                               COL_NAME,
1266                                               GTK_SORT_ASCENDING);
1267
1268         gtk_tree_view_set_model (GTK_TREE_VIEW (list), model);
1269
1270         g_object_unref (model);
1271 }
1272
1273 static gboolean
1274 contact_list_search_equal_func (GtkTreeModel *model,
1275                                 gint          column,
1276                                 const gchar  *key,
1277                                 GtkTreeIter  *iter,
1278                                 gpointer      search_data)
1279 {
1280         gchar    *name, *name_folded;
1281         gchar    *key_folded;
1282         gboolean  ret;
1283
1284         gtk_tree_model_get (model, iter,
1285                             COL_NAME, &name,
1286                             -1);
1287
1288         name_folded = g_utf8_casefold (name, -1);
1289         key_folded = g_utf8_casefold (key, -1);
1290
1291         if (strstr (name_folded, key_folded)) {
1292                 ret = FALSE;
1293         } else {
1294                 ret = TRUE;
1295         }
1296
1297         g_free (name);
1298         g_free (name_folded);
1299         g_free (key_folded);
1300
1301         return ret;
1302 }
1303
1304 static void
1305 contact_list_setup_view (GossipContactList *list)
1306 {
1307         GtkCellRenderer   *cell;
1308         GtkTreeViewColumn *col;
1309         gint               i;
1310
1311         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1312                                              contact_list_search_equal_func,
1313                                              list,
1314                                              NULL);
1315
1316         g_object_set (list,
1317                       "headers-visible", FALSE,
1318                       "reorderable", TRUE,
1319                       "show-expanders", FALSE,
1320                       NULL);
1321
1322         col = gtk_tree_view_column_new ();
1323
1324         /* State */
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 (
1328                 col, cell,
1329                 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1330                 list, NULL);
1331
1332         g_object_set (cell,
1333                       "xpad", 5,
1334                       "ypad", 1,
1335                       "visible", FALSE,
1336                       NULL);
1337
1338         /* Name */
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 (
1342                 col, cell,
1343                 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1344                 list, NULL);
1345
1346         gtk_tree_view_column_add_attribute (col, cell,
1347                                             "name", COL_NAME);
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);
1352
1353         /* Avatar */
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 (
1357                 col, cell,
1358                 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1359                 list, NULL);
1360
1361         g_object_set (cell,
1362                       "xpad", 0,
1363                       "ypad", 0,
1364                       "visible", FALSE,
1365                       "width", 32,
1366                       "height", 32,
1367                       NULL);
1368
1369         /* Expander */
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 (
1373                 col, cell,
1374                 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1375                 list, NULL);
1376
1377         /* Actually add the column now we have added all cell renderers */
1378         gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1379
1380         /* Drag & Drop. */
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,
1383                                                       FALSE);
1384         }
1385
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,
1388                                                         FALSE);
1389         }
1390
1391         /* Note: We support the COPY action too, but need to make the
1392          * MOVE action the default.
1393          */
1394         gtk_drag_source_set (GTK_WIDGET (list),
1395                              GDK_BUTTON1_MASK,
1396                              drag_types_source,
1397                              G_N_ELEMENTS (drag_types_source),
1398                              GDK_ACTION_MOVE);
1399
1400         gtk_drag_dest_set (GTK_WIDGET (list),
1401                            GTK_DEST_DEFAULT_ALL,
1402                            drag_types_dest,
1403                            G_N_ELEMENTS (drag_types_dest),
1404                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
1405
1406         g_signal_connect (GTK_WIDGET (list),
1407                           "drag-data-received",
1408                           G_CALLBACK (contact_list_drag_data_received),
1409                           NULL);
1410
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.
1414          */
1415         g_signal_connect (GTK_WIDGET (list),
1416                           "drag-motion",
1417                           G_CALLBACK (contact_list_drag_motion),
1418                           NULL);
1419
1420         g_signal_connect (GTK_WIDGET (list),
1421                           "drag-begin",
1422                           G_CALLBACK (contact_list_drag_begin),
1423                           NULL);
1424         g_signal_connect (GTK_WIDGET (list),
1425                           "drag-data-get",
1426                           G_CALLBACK (contact_list_drag_data_get),
1427                           NULL);
1428         g_signal_connect (GTK_WIDGET (list),
1429                           "drag-end",
1430                           G_CALLBACK (contact_list_drag_end),
1431                           NULL);
1432 }
1433
1434 static void
1435 contact_list_drag_data_received (GtkWidget         *widget,
1436                                  GdkDragContext    *context,
1437                                  gint               x,
1438                                  gint               y,
1439                                  GtkSelectionData  *selection,
1440                                  guint              info,
1441                                  guint              time,
1442                                  gpointer           user_data)
1443 {
1444         GossipContactListPriv   *priv;
1445         GtkTreeModel            *model;
1446         GtkTreePath             *path;
1447         GtkTreeViewDropPosition  position;
1448         GossipContact           *contact;
1449         GList                   *groups;
1450         const gchar             *id;
1451         gchar                   *old_group;
1452         gboolean                 is_row;
1453         gboolean                 drag_success = TRUE;
1454         gboolean                 drag_del = FALSE;
1455
1456         priv = GET_PRIV (widget);
1457
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" : "",
1462                       id);
1463
1464         /* FIXME: This is ambigous, an id can come from multiple accounts */
1465         contact = empathy_contact_manager_find (priv->manager, id);
1466         if (!contact) {
1467                 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1468                 return;
1469         }
1470
1471         groups = gossip_contact_get_groups (contact);
1472
1473         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1474                                                     x,
1475                                                     y,
1476                                                     &path,
1477                                                     &position);
1478
1479         if (!is_row) {
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?
1487                          */
1488                         return;
1489                 }
1490
1491                 gossip_contact_set_groups (contact, NULL);
1492         } else {
1493                 GList    *l, *new_groups;
1494                 gchar    *name;
1495                 gboolean  is_group;
1496
1497                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1498                 name = contact_list_get_parent_group (model, path, &is_group);
1499
1500                 if (groups && name &&
1501                     g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1502                         g_free (name);
1503                         return;
1504                 }
1505
1506                 /* Get source group information. */
1507                 priv = GET_PRIV (widget);
1508                 if (!priv->drag_row) {
1509                         g_free (name);
1510                         return;
1511                 }
1512
1513                 path = gtk_tree_row_reference_get_path (priv->drag_row);
1514                 if (!path) {
1515                         g_free (name);
1516                         return;
1517                 }
1518
1519                 old_group = contact_list_get_parent_group (model, path, &is_group);
1520                 gtk_tree_path_free (path);
1521
1522                 if (!name && old_group && GDK_ACTION_MOVE) {
1523                         drag_success = FALSE;
1524                 }
1525
1526                 if (context->action == GDK_ACTION_MOVE) {
1527                         drag_del = TRUE;
1528                 }
1529
1530                 /* Create new groups GList. */
1531                 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1532                         gchar *str;
1533
1534                         str = l->data;
1535                         if (context->action == GDK_ACTION_MOVE &&
1536                             old_group != NULL &&
1537                             strcmp (str, old_group) == 0) {
1538                                 continue;
1539                         }
1540
1541                         if (str == NULL) {
1542                                 continue;
1543                         }
1544
1545                         new_groups = g_list_append (new_groups, g_strdup (str));
1546                 }
1547
1548                 if (drag_success) {
1549                         if (name) {
1550                                 new_groups = g_list_append (new_groups, name);
1551                         }
1552                         gossip_contact_set_groups (contact, new_groups);
1553                 } else {
1554                         g_free (name);
1555                 }
1556         }
1557
1558         gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1559 }
1560
1561 static gboolean
1562 contact_list_drag_motion (GtkWidget      *widget,
1563                           GdkDragContext *context,
1564                           gint            x,
1565                           gint            y,
1566                           guint           time,
1567                           gpointer        data)
1568 {
1569         static DragMotionData *dm = NULL;
1570         GtkTreePath           *path;
1571         gboolean               is_row;
1572         gboolean               is_different = FALSE;
1573         gboolean               cleanup = TRUE;
1574
1575         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1576                                                 x,
1577                                                 y,
1578                                                 &path,
1579                                                 NULL,
1580                                                 NULL,
1581                                                 NULL);
1582
1583         cleanup &= (!dm);
1584
1585         if (is_row) {
1586                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1587                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1588         } else {
1589                 cleanup &= FALSE;
1590         }
1591
1592         if (!is_different && !cleanup) {
1593                 return TRUE;
1594         }
1595
1596         if (dm) {
1597                 gtk_tree_path_free (dm->path);
1598                 if (dm->timeout_id) {
1599                         g_source_remove (dm->timeout_id);
1600                 }
1601
1602                 g_free (dm);
1603
1604                 dm = NULL;
1605         }
1606
1607         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1608                 dm = g_new0 (DragMotionData, 1);
1609
1610                 dm->list = GOSSIP_CONTACT_LIST (widget);
1611                 dm->path = gtk_tree_path_copy (path);
1612
1613                 dm->timeout_id = g_timeout_add (
1614                         1500,
1615                         (GSourceFunc) contact_list_drag_motion_cb,
1616                         dm);
1617         }
1618
1619         return TRUE;
1620 }
1621
1622 static gboolean
1623 contact_list_drag_motion_cb (DragMotionData *data)
1624 {
1625         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1626                                   data->path,
1627                                   FALSE);
1628
1629         data->timeout_id = 0;
1630
1631         return FALSE;
1632 }
1633
1634 static void
1635 contact_list_drag_begin (GtkWidget      *widget,
1636                          GdkDragContext *context,
1637                          gpointer        user_data)
1638 {
1639         GossipContactListPriv *priv;
1640         GtkTreeSelection      *selection;
1641         GtkTreeModel          *model;
1642         GtkTreePath           *path;
1643         GtkTreeIter            iter;
1644
1645         priv = GET_PRIV (widget);
1646
1647         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1648         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1649                 return;
1650         }
1651
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);
1655 }
1656
1657 static void
1658 contact_list_drag_data_get (GtkWidget             *widget,
1659                             GdkDragContext        *context,
1660                             GtkSelectionData      *selection,
1661                             guint                  info,
1662                             guint                  time,
1663                             gpointer               user_data)
1664 {
1665         GossipContactListPriv *priv;
1666         GtkTreePath           *src_path;
1667         GtkTreeIter            iter;
1668         GtkTreeModel          *model;
1669         GossipContact         *contact;
1670         const gchar           *id;
1671
1672         priv = GET_PRIV (widget);
1673
1674         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1675         if (!priv->drag_row) {
1676                 return;
1677         }
1678
1679         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1680         if (!src_path) {
1681                 return;
1682         }
1683
1684         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1685                 gtk_tree_path_free (src_path);
1686                 return;
1687         }
1688
1689         gtk_tree_path_free (src_path);
1690
1691         contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1692         if (!contact) {
1693                 return;
1694         }
1695
1696         id = gossip_contact_get_id (contact);
1697         g_object_unref (contact);
1698
1699         switch (info) {
1700         case DND_DRAG_TYPE_CONTACT_ID:
1701                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1702                                         (guchar*)id, strlen (id) + 1);
1703                 break;
1704
1705         default:
1706                 return;
1707         }
1708 }
1709
1710 static void
1711 contact_list_drag_end (GtkWidget      *widget,
1712                        GdkDragContext *context,
1713                        gpointer        user_data)
1714 {
1715         GossipContactListPriv *priv;
1716
1717         priv = GET_PRIV (widget);
1718
1719         if (priv->drag_row) {
1720                 gtk_tree_row_reference_free (priv->drag_row);
1721                 priv->drag_row = NULL;
1722         }
1723 }
1724
1725 static void
1726 contact_list_cell_set_background (GossipContactList  *list,
1727                                   GtkCellRenderer    *cell,
1728                                   gboolean            is_group,
1729                                   gboolean            is_active)
1730 {
1731         GdkColor  color;
1732         GtkStyle *style;
1733         gint color_sum_normal, color_sum_selected;
1734
1735         g_return_if_fail (list != NULL);
1736         g_return_if_fail (cell != NULL);
1737
1738         style = gtk_widget_get_style (GTK_WIDGET (list));
1739
1740         if (!is_group) {
1741                 if (is_active) {
1742                         color = style->bg[GTK_STATE_SELECTED];
1743
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
1747                          * slightly whiter.
1748                          */
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;
1752
1753                         g_object_set (cell,
1754                                       "cell-background-gdk", &color,
1755                                       NULL);
1756                 } else {
1757                         g_object_set (cell,
1758                                       "cell-background-gdk", NULL,
1759                                       NULL);
1760                 }
1761         } else {
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];
1767
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;
1773                 } else { 
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;
1778                 }
1779
1780                 g_object_set (cell,
1781                               "cell-background-gdk", &color,
1782                               NULL);
1783         }
1784 }
1785
1786 static void
1787 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1788                                     GtkCellRenderer   *cell,
1789                                     GtkTreeModel      *model,
1790                                     GtkTreeIter       *iter,
1791                                     GossipContactList *list)
1792 {
1793         GdkPixbuf *pixbuf;
1794         gboolean   is_group;
1795         gboolean   is_active;
1796
1797         gtk_tree_model_get (model, iter,
1798                             COL_IS_GROUP, &is_group,
1799                             COL_IS_ACTIVE, &is_active,
1800                             COL_PIXBUF_STATUS, &pixbuf,
1801                             -1);
1802
1803         g_object_set (cell,
1804                       "visible", !is_group,
1805                       "pixbuf", pixbuf,
1806                       NULL);
1807
1808         if (pixbuf) {
1809                 g_object_unref (pixbuf);
1810         }
1811
1812         contact_list_cell_set_background (list, cell, is_group, is_active);
1813 }
1814
1815 static void
1816 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1817                                     GtkCellRenderer   *cell,
1818                                     GtkTreeModel      *model,
1819                                     GtkTreeIter       *iter,
1820                                     GossipContactList *list)
1821 {
1822         GdkPixbuf *pixbuf;
1823         gboolean   show_avatar;
1824         gboolean   is_group;
1825         gboolean   is_active;
1826
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,
1832                             -1);
1833
1834         g_object_set (cell,
1835                       "visible", !is_group && show_avatar,
1836                       "pixbuf", pixbuf,
1837                       NULL);
1838
1839         if (pixbuf) {
1840                 g_object_unref (pixbuf);
1841         }
1842
1843         contact_list_cell_set_background (list, cell, is_group, is_active);
1844 }
1845
1846 static void
1847 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
1848                                   GtkCellRenderer   *cell,
1849                                   GtkTreeModel      *model,
1850                                   GtkTreeIter       *iter,
1851                                   GossipContactList *list)
1852 {
1853         gboolean is_group;
1854         gboolean is_active;
1855         gboolean show_status;
1856
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,
1861                             -1);
1862
1863         g_object_set (cell,
1864                       "show-status", show_status,
1865                       NULL);
1866
1867         contact_list_cell_set_background (list, cell, is_group, is_active);
1868 }
1869
1870 static void
1871 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
1872                                       GtkCellRenderer   *cell,
1873                                       GtkTreeModel      *model,
1874                                       GtkTreeIter       *iter,
1875                                       GossipContactList *list)
1876 {
1877         gboolean is_group;
1878         gboolean is_active;
1879
1880         gtk_tree_model_get (model, iter,
1881                             COL_IS_GROUP, &is_group,
1882                             COL_IS_ACTIVE, &is_active,
1883                             -1);
1884
1885         if (gtk_tree_model_iter_has_child (model, iter)) {
1886                 GtkTreePath *path;
1887                 gboolean     row_expanded;
1888
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);
1892
1893                 g_object_set (cell,
1894                               "visible", TRUE,
1895                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1896                               NULL);
1897         } else {
1898                 g_object_set (cell, "visible", FALSE, NULL);
1899         }
1900
1901         contact_list_cell_set_background (list, cell, is_group, is_active);
1902 }
1903
1904 static GtkWidget *
1905 contact_list_get_contact_menu (GossipContactList *list,
1906                                gboolean           can_send_file,
1907                                gboolean           can_show_log)
1908 {
1909         GossipContactListPriv *priv;
1910         GtkAction             *action;
1911         GtkWidget             *widget;
1912
1913         priv = GET_PRIV (list);
1914
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);
1918
1919         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
1920         gtk_action_set_visible (action, can_send_file);
1921
1922         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
1923
1924         return widget;
1925 }
1926
1927 GtkWidget *
1928 gossip_contact_list_get_group_menu (GossipContactList *list)
1929 {
1930         GossipContactListPriv *priv;
1931         GtkWidget             *widget;
1932
1933         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
1934
1935         priv = GET_PRIV (list);
1936
1937         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
1938
1939         return widget;
1940 }
1941
1942 GtkWidget *
1943 gossip_contact_list_get_contact_menu (GossipContactList *list,
1944                                       GossipContact     *contact)
1945 {
1946         GtkWidget *menu;
1947         gboolean   can_show_log;
1948         gboolean   can_send_file;
1949
1950         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
1951         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
1952
1953         can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
1954         can_send_file = FALSE;
1955
1956         menu = contact_list_get_contact_menu (list,
1957                                               can_send_file,
1958                                               can_show_log);
1959         return menu;
1960 }
1961
1962 static gboolean
1963 contact_list_button_press_event_cb (GossipContactList *list,
1964                                     GdkEventButton    *event,
1965                                     gpointer           user_data)
1966 {
1967         GossipContactListPriv *priv;
1968         GossipContact         *contact;
1969         GtkTreePath           *path;
1970         GtkTreeSelection      *selection;
1971         GtkTreeModel          *model;
1972         GtkTreeIter            iter;
1973         gboolean               row_exists;
1974         GtkWidget             *menu;
1975
1976         if (event->button != 3) {
1977                 return FALSE;
1978         }
1979
1980         priv = GET_PRIV (list);
1981
1982         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
1983         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
1984
1985         gtk_widget_grab_focus (GTK_WIDGET (list));
1986
1987         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
1988                                                     event->x, event->y,
1989                                                     &path,
1990                                                     NULL, NULL, NULL);
1991         if (!row_exists) {
1992                 return FALSE;
1993         }
1994
1995         gtk_tree_selection_unselect_all (selection);
1996         gtk_tree_selection_select_path (selection, path);
1997
1998         gtk_tree_model_get_iter (model, &iter, path);
1999         gtk_tree_path_free (path);
2000
2001         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2002
2003         if (contact) {
2004                 menu = gossip_contact_list_get_contact_menu (list, contact);
2005                 g_object_unref (contact);
2006         } else {
2007                 menu = gossip_contact_list_get_group_menu (list);
2008         }
2009
2010         if (!menu) {
2011                 return FALSE;
2012         }
2013
2014         gtk_widget_show (menu);
2015
2016         gtk_menu_popup (GTK_MENU (menu),
2017                         NULL, NULL, NULL, NULL,
2018                         event->button, event->time);
2019
2020         return TRUE;
2021 }
2022
2023 static void
2024 contact_list_row_activated_cb (GossipContactList *list,
2025                                GtkTreePath       *path,
2026                                GtkTreeViewColumn *col,
2027                                gpointer           user_data)
2028 {
2029         GossipContact *contact;
2030         GtkTreeView   *view;
2031         GtkTreeModel  *model;
2032         GtkTreeIter    iter;
2033
2034         view = GTK_TREE_VIEW (list);
2035         model = gtk_tree_view_get_model (view);
2036
2037         gtk_tree_model_get_iter (model, &iter, path);
2038         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2039
2040         if (contact) {
2041                 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2042                 g_object_unref (contact);
2043         }
2044 }
2045
2046 static void
2047 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2048                                         GtkTreeIter       *iter,
2049                                         GtkTreePath       *path,
2050                                         gpointer           user_data)
2051 {
2052         GtkTreeModel *model;
2053         gchar        *name;
2054         gboolean      expanded;
2055
2056         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2057
2058         gtk_tree_model_get (model, iter,
2059                             COL_NAME, &name,
2060                             -1);
2061
2062         expanded = GPOINTER_TO_INT (user_data);
2063         gossip_contact_group_set_expanded (name, expanded);
2064
2065         g_free (name);
2066 }
2067
2068 static gint
2069 contact_list_sort_func (GtkTreeModel *model,
2070                         GtkTreeIter  *iter_a,
2071                         GtkTreeIter  *iter_b,
2072                         gpointer      user_data)
2073 {
2074         gchar         *name_a, *name_b;
2075         GossipContact *contact_a, *contact_b;
2076         gint           ret_val;
2077
2078         gtk_tree_model_get (model, iter_a,
2079                             COL_NAME, &name_a,
2080                             COL_CONTACT, &contact_a,
2081                             -1);
2082         gtk_tree_model_get (model, iter_b,
2083                             COL_NAME, &name_b,
2084                             COL_CONTACT, &contact_b,
2085                             -1);
2086
2087         /* If contact is NULL it means it's a group. */
2088
2089         if (!contact_a && contact_b) {
2090                 ret_val = 1;
2091         } else if (contact_a && !contact_b) {
2092                 ret_val = -1;
2093         } else {
2094                 ret_val = g_utf8_collate (name_a, name_b);
2095         }
2096
2097         g_free (name_a);
2098         g_free (name_b);
2099
2100         if (contact_a) {
2101                 g_object_unref (contact_a);
2102         }
2103
2104         if (contact_b) {
2105                 g_object_unref (contact_b);
2106         }
2107
2108         return ret_val;
2109 }
2110
2111 static gboolean
2112 contact_list_iter_equal_contact (GtkTreeModel  *model,
2113                                  GtkTreeIter   *iter,
2114                                  GossipContact *contact)
2115 {
2116         GossipContact *c;
2117         gboolean       equal;
2118
2119         gtk_tree_model_get (model, iter,
2120                             COL_CONTACT, &c,
2121                             -1);
2122
2123         if (!c) {
2124                 return FALSE;
2125         }
2126
2127         equal = (c == contact);
2128         g_object_unref (c);
2129
2130         return equal;
2131 }
2132
2133 static GList *
2134 contact_list_find_contact (GossipContactList *list,
2135                            GossipContact     *contact)
2136 {
2137         GtkTreeModel *model;
2138         FindContact   fc;
2139         GList        *l = NULL;
2140
2141         memset (&fc, 0, sizeof (fc));
2142
2143         fc.contact = contact;
2144
2145         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2146         gtk_tree_model_foreach (model,
2147                                 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2148                                 &fc);
2149
2150         if (fc.found) {
2151                 l = fc.iters;
2152         }
2153
2154         return l;
2155 }
2156
2157 static gboolean
2158 contact_list_find_contact_foreach (GtkTreeModel *model,
2159                                    GtkTreePath  *path,
2160                                    GtkTreeIter  *iter,
2161                                    FindContact  *fc)
2162 {
2163         if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2164                 fc->found = TRUE;
2165                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2166         }
2167
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
2170          * returned.
2171          */
2172         return FALSE;
2173 }
2174
2175 static void
2176 contact_list_action_cb (GtkAction         *action,
2177                         GossipContactList *list)
2178 {
2179         GossipContact *contact;
2180         const gchar   *name;
2181         gchar         *group;
2182
2183         name = gtk_action_get_name (action);
2184         if (!name) {
2185                 return;
2186         }
2187
2188         gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2189
2190         contact = gossip_contact_list_get_selected (list);
2191         group = gossip_contact_list_get_selected_group (list);
2192
2193         if (contact && strcmp (name, "Chat") == 0) {
2194                 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2195         }
2196         else if (contact && strcmp (name, "Information") == 0) {
2197                 g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
2198         }
2199         else if (contact && strcmp (name, "Edit") == 0) {
2200                 g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
2201         }
2202         else if (contact && strcmp (name, "Remove") == 0) {
2203                 g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
2204         }
2205         else if (contact && strcmp (name, "Invite") == 0) {
2206                 g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
2207         }
2208         else if (contact && strcmp (name, "SendFile") == 0) {
2209                 g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
2210         }
2211         else if (contact && strcmp (name, "Log") == 0) {
2212                 g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
2213         }
2214         else if (group && strcmp (name, "Rename") == 0) {
2215                 g_signal_emit (list, signals[GROUP_RENAME], 0, group);
2216         }
2217
2218         g_free (group);
2219         if (contact) {
2220                 g_object_unref (contact);
2221         }
2222 }
2223
2224 static gboolean
2225 contact_list_update_list_mode_foreach (GtkTreeModel      *model,
2226                                        GtkTreePath       *path,
2227                                        GtkTreeIter       *iter,
2228                                        GossipContactList *list)
2229 {
2230         GossipContactListPriv *priv;
2231         gboolean               show_avatar = FALSE;
2232
2233         priv = GET_PRIV (list);
2234
2235         if (priv->show_avatars && !priv->is_compact) {
2236                 show_avatar = TRUE;
2237         }
2238
2239         gtk_tree_store_set (GTK_TREE_STORE (model), iter,
2240                             COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2241                             COL_STATUS_VISIBLE, !priv->is_compact,
2242                             -1);
2243
2244         return FALSE;
2245 }
2246
2247 GossipContactList *
2248 gossip_contact_list_new (void)
2249 {
2250         return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2251 }
2252
2253 GossipContact *
2254 gossip_contact_list_get_selected (GossipContactList *list)
2255 {
2256         GossipContactListPriv *priv;
2257         GtkTreeSelection      *selection;
2258         GtkTreeIter            iter;
2259         GtkTreeModel          *model;
2260         GossipContact         *contact;
2261
2262         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2263
2264         priv = GET_PRIV (list);
2265
2266         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2267         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2268                 return NULL;
2269         }
2270
2271         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2272
2273         return contact;
2274 }
2275
2276 gchar *
2277 gossip_contact_list_get_selected_group (GossipContactList *list)
2278 {
2279         GossipContactListPriv *priv;
2280         GtkTreeSelection      *selection;
2281         GtkTreeIter            iter;
2282         GtkTreeModel          *model;
2283         gboolean               is_group;
2284         gchar                 *name;
2285
2286         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2287
2288         priv = GET_PRIV (list);
2289
2290         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2291         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2292                 return NULL;
2293         }
2294
2295         gtk_tree_model_get (model, &iter,
2296                             COL_IS_GROUP, &is_group,
2297                             COL_NAME, &name,
2298                             -1);
2299
2300         if (!is_group) {
2301                 g_free (name);
2302                 return NULL;
2303         }
2304
2305         return name;
2306 }
2307
2308 gboolean
2309 gossip_contact_list_get_show_offline (GossipContactList *list)
2310 {
2311         GossipContactListPriv *priv;
2312
2313         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2314
2315         priv = GET_PRIV (list);
2316
2317         return priv->show_offline;
2318 }
2319
2320 void
2321 gossip_contact_list_set_show_offline (GossipContactList *list,
2322                                       gboolean           show_offline)
2323 {
2324         GossipContactListPriv *priv;
2325         GList                 *contacts, *l;
2326         gboolean               show_active;
2327
2328         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2329
2330         priv = GET_PRIV (list);
2331
2332         priv->show_offline = show_offline;
2333         show_active = priv->show_active;
2334
2335         /* Disable temporarily. */
2336         priv->show_active = FALSE;
2337
2338         contacts = empathy_contact_manager_get_contacts (priv->manager);
2339         for (l = contacts; l; l = l->next) {
2340                 GossipContact *contact;
2341
2342                 contact = GOSSIP_CONTACT (l->data);
2343
2344                 contact_list_contact_update (list, contact);
2345                 
2346                 g_object_unref (contact);
2347         }
2348         g_list_free (contacts);
2349
2350         /* Restore to original setting. */
2351         priv->show_active = show_active;
2352 }
2353
2354 gboolean
2355 gossip_contact_list_get_show_avatars (GossipContactList *list)
2356 {
2357         GossipContactListPriv *priv;
2358
2359         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2360
2361         priv = GET_PRIV (list);
2362
2363         return priv->show_avatars;
2364 }
2365
2366 void
2367 gossip_contact_list_set_show_avatars (GossipContactList *list,
2368                                       gboolean           show_avatars)
2369 {
2370         GossipContactListPriv *priv;
2371         GtkTreeModel          *model;
2372
2373         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2374
2375         priv = GET_PRIV (list);
2376
2377         priv->show_avatars = show_avatars;
2378
2379         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2380
2381         gtk_tree_model_foreach (model,
2382                                 (GtkTreeModelForeachFunc)
2383                                 contact_list_update_list_mode_foreach,
2384                                 list);
2385 }
2386
2387 gboolean
2388 gossip_contact_list_get_is_compact (GossipContactList *list)
2389 {
2390         GossipContactListPriv *priv;
2391
2392         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2393
2394         priv = GET_PRIV (list);
2395
2396         return priv->is_compact;
2397 }
2398
2399 void
2400 gossip_contact_list_set_is_compact (GossipContactList *list,
2401                                     gboolean           is_compact)
2402 {
2403         GossipContactListPriv *priv;
2404         GtkTreeModel          *model;
2405
2406         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2407
2408         priv = GET_PRIV (list);
2409
2410         priv->is_compact = is_compact;
2411
2412         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2413
2414         gtk_tree_model_foreach (model,
2415                                 (GtkTreeModelForeachFunc)
2416                                 contact_list_update_list_mode_foreach,
2417                                 list);
2418 }
2419