a96c28e51939df91facb7bd36ddc87242fe52e8e
[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         GtkTreeStore          *store;
76         GtkTreeModel          *filter;
77         gchar                 *filter_text;
78
79         gboolean               show_offline;
80         gboolean               show_avatars;
81         gboolean               is_compact;
82         gboolean               show_active;
83 };
84
85 typedef struct {
86         GtkTreeIter  iter;
87         const gchar *name;
88         gboolean     found;
89 } FindGroup;
90
91 typedef struct {
92         GossipContact *contact;
93         gboolean       found;
94         GList         *iters;
95 } FindContact;
96
97 typedef struct {
98         GossipContactList *list;
99         GtkTreePath       *path;
100         guint              timeout_id;
101 } DragMotionData;
102
103 typedef struct {
104         GossipContactList *list;
105         GossipContact     *contact;
106         gboolean           remove;
107 } ShowActiveData;
108
109 static void     gossip_contact_list_class_init               (GossipContactListClass *klass);
110 static void     gossip_contact_list_init                     (GossipContactList      *list);
111 static void     contact_list_finalize                        (GObject                *object);
112 static void     contact_list_get_property                    (GObject                *object,
113                                                               guint                   param_id,
114                                                               GValue                 *value,
115                                                               GParamSpec             *pspec);
116 static void     contact_list_set_property                    (GObject                *object,
117                                                               guint                   param_id,
118                                                               const GValue           *value,
119                                                               GParamSpec             *pspec);
120 static gboolean contact_list_row_separator_func              (GtkTreeModel           *model,
121                                                               GtkTreeIter            *iter,
122                                                               gpointer                data);
123 static void     contact_list_contact_update                  (GossipContactList      *list,
124                                                               GossipContact          *contact);
125 static void     contact_list_contact_added_cb                (EmpathyContactManager  *manager,
126                                                               GossipContact          *contact,
127                                                               GossipContactList      *list);
128 static void     contact_list_contact_updated_cb              (GossipContact          *contact,
129                                                               GParamSpec             *param,
130                                                               GossipContactList      *list);
131 static void     contact_list_contact_groups_updated_cb       (GossipContact          *contact,
132                                                               GParamSpec             *param,
133                                                               GossipContactList      *list);
134 static void     contact_list_contact_removed_cb              (EmpathyContactManager  *manager,
135                                                               GossipContact          *contact,
136                                                               GossipContactList      *list);
137 static void     contact_list_contact_set_active              (GossipContactList      *list,
138                                                               GossipContact          *contact,
139                                                               gboolean                active,
140                                                               gboolean                set_changed);
141 static ShowActiveData *
142                 contact_list_contact_active_new              (GossipContactList      *list,
143                                                               GossipContact          *contact,
144                                                               gboolean                remove);
145 static void     contact_list_contact_active_free             (ShowActiveData         *data);
146 static gboolean contact_list_contact_active_cb               (ShowActiveData         *data);
147 static gchar *  contact_list_get_parent_group                (GtkTreeModel           *model,
148                                                               GtkTreePath            *path,
149                                                               gboolean               *path_is_group);
150 static void     contact_list_get_group                       (GossipContactList      *list,
151                                                               const gchar            *name,
152                                                               GtkTreeIter            *iter_group_to_set,
153                                                               GtkTreeIter            *iter_separator_to_set,
154                                                               gboolean               *created);
155 static gboolean contact_list_get_group_foreach               (GtkTreeModel           *model,
156                                                               GtkTreePath            *path,
157                                                               GtkTreeIter            *iter,
158                                                               FindGroup              *fg);
159 static void     contact_list_add_contact                     (GossipContactList      *list,
160                                                               GossipContact          *contact);
161 static void     contact_list_remove_contact                  (GossipContactList      *list,
162                                                               GossipContact          *contact);
163 static void     contact_list_create_model                    (GossipContactList      *list);
164 static gboolean contact_list_search_equal_func               (GtkTreeModel           *model,
165                                                               gint                    column,
166                                                               const gchar            *key,
167                                                               GtkTreeIter            *iter,
168                                                               gpointer                search_data);
169 static void     contact_list_setup_view                      (GossipContactList      *list);
170 static void     contact_list_drag_data_received              (GtkWidget              *widget,
171                                                               GdkDragContext         *context,
172                                                               gint                    x,
173                                                               gint                    y,
174                                                               GtkSelectionData       *selection,
175                                                               guint                   info,
176                                                               guint                   time,
177                                                               gpointer                user_data);
178 static gboolean contact_list_drag_motion                     (GtkWidget              *widget,
179                                                               GdkDragContext         *context,
180                                                               gint                    x,
181                                                               gint                    y,
182                                                               guint                   time,
183                                                               gpointer                data);
184 static gboolean contact_list_drag_motion_cb                  (DragMotionData         *data);
185 static void     contact_list_drag_begin                      (GtkWidget              *widget,
186                                                               GdkDragContext         *context,
187                                                               gpointer                user_data);
188 static void     contact_list_drag_data_get                   (GtkWidget              *widget,
189                                                               GdkDragContext         *contact,
190                                                               GtkSelectionData       *selection,
191                                                               guint                   info,
192                                                               guint                   time,
193                                                               gpointer                user_data);
194 static void     contact_list_drag_end                        (GtkWidget              *widget,
195                                                               GdkDragContext         *context,
196                                                               gpointer                user_data);
197 static void     contact_list_cell_set_background             (GossipContactList      *list,
198                                                               GtkCellRenderer        *cell,
199                                                               gboolean                is_group,
200                                                               gboolean                is_active);
201 static void     contact_list_pixbuf_cell_data_func           (GtkTreeViewColumn      *tree_column,
202                                                               GtkCellRenderer        *cell,
203                                                               GtkTreeModel           *model,
204                                                               GtkTreeIter            *iter,
205                                                               GossipContactList      *list);
206 static void     contact_list_avatar_cell_data_func           (GtkTreeViewColumn      *tree_column,
207                                                               GtkCellRenderer        *cell,
208                                                               GtkTreeModel           *model,
209                                                               GtkTreeIter            *iter,
210                                                               GossipContactList      *list);
211 static void     contact_list_text_cell_data_func             (GtkTreeViewColumn      *tree_column,
212                                                               GtkCellRenderer        *cell,
213                                                               GtkTreeModel           *model,
214                                                               GtkTreeIter            *iter,
215                                                               GossipContactList      *list);
216 static void     contact_list_expander_cell_data_func         (GtkTreeViewColumn      *column,
217                                                               GtkCellRenderer        *cell,
218                                                               GtkTreeModel           *model,
219                                                               GtkTreeIter            *iter,
220                                                               GossipContactList      *list);
221 static GtkWidget *contact_list_get_contact_menu              (GossipContactList      *list,
222                                                               gboolean                can_send_file,
223                                                               gboolean                can_show_log);
224 static gboolean contact_list_button_press_event_cb           (GossipContactList      *list,
225                                                               GdkEventButton         *event,
226                                                               gpointer                user_data);
227 static void     contact_list_row_activated_cb                (GossipContactList      *list,
228                                                               GtkTreePath            *path,
229                                                               GtkTreeViewColumn      *col,
230                                                               gpointer                user_data);
231 static void     contact_list_row_expand_or_collapse_cb       (GossipContactList      *list,
232                                                               GtkTreeIter            *iter,
233                                                               GtkTreePath            *path,
234                                                               gpointer                user_data);
235 static gint     contact_list_sort_func                       (GtkTreeModel           *model,
236                                                               GtkTreeIter            *iter_a,
237                                                               GtkTreeIter            *iter_b,
238                                                               gpointer                user_data);
239 static gboolean contact_list_filter_func                     (GtkTreeModel           *model,
240                                                               GtkTreeIter            *iter,
241                                                               GossipContactList      *list);
242 static GList *  contact_list_find_contact                    (GossipContactList      *list,
243                                                               GossipContact          *contact);
244 static gboolean contact_list_find_contact_foreach            (GtkTreeModel           *model,
245                                                               GtkTreePath            *path,
246                                                               GtkTreeIter            *iter,
247                                                               FindContact            *fc);
248 static void     contact_list_action_cb                       (GtkAction              *action,
249                                                               GossipContactList      *list);
250 static gboolean contact_list_update_list_mode_foreach        (GtkTreeModel           *model,
251                                                               GtkTreePath            *path,
252                                                               GtkTreeIter            *iter,
253                                                               GossipContactList      *list);
254 enum {
255         CONTACT_CHAT,
256         CONTACT_INFORMATION,
257         CONTACT_EDIT,
258         CONTACT_REMOVE,
259         CONTACT_INVITE,
260         CONTACT_SEND_FILE,
261         CONTACT_LOG,
262         GROUP_RENAME,
263         LAST_SIGNAL
264 };
265
266 static guint signals[LAST_SIGNAL];
267
268 enum {
269         COL_PIXBUF_STATUS,
270         COL_PIXBUF_AVATAR,
271         COL_PIXBUF_AVATAR_VISIBLE,
272         COL_NAME,
273         COL_STATUS,
274         COL_STATUS_VISIBLE,
275         COL_CONTACT,
276         COL_IS_GROUP,
277         COL_IS_ACTIVE,
278         COL_IS_ONLINE,
279         COL_IS_SEPARATOR,
280         COL_COUNT
281 };
282
283 enum {
284         PROP_0,
285         PROP_SHOW_OFFLINE,
286         PROP_SHOW_AVATARS,
287         PROP_IS_COMPACT,
288         PROP_FILTER
289 };
290
291 static const GtkActionEntry entries[] = {
292         { "ContactMenu", NULL,
293           N_("_Contact"), NULL, NULL,
294           NULL
295         },
296         { "GroupMenu", NULL,
297           N_("_Group"),NULL, NULL,
298           NULL
299         },
300         { "Chat", GOSSIP_STOCK_MESSAGE,
301           N_("_Chat"), NULL, N_("Chat with contact"),
302           G_CALLBACK (contact_list_action_cb)
303         },
304         { "Information", GOSSIP_STOCK_CONTACT_INFORMATION,
305           N_("Infor_mation"), "<control>I", N_("View contact information"),
306           G_CALLBACK (contact_list_action_cb)
307         },
308         { "Rename", NULL,
309           N_("Re_name"), NULL, N_("Rename"),
310           G_CALLBACK (contact_list_action_cb)
311         },
312         { "Edit", GTK_STOCK_EDIT,
313           N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
314           G_CALLBACK (contact_list_action_cb)
315         },
316         { "Remove", GTK_STOCK_REMOVE,
317           N_("_Remove"), NULL, N_("Remove contact"),
318           G_CALLBACK (contact_list_action_cb)
319         },
320         { "Invite", GOSSIP_STOCK_GROUP_MESSAGE,
321           N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
322           G_CALLBACK (contact_list_action_cb)
323         },
324         { "SendFile", NULL,
325           N_("_Send File..."), NULL, N_("Send a file"),
326           G_CALLBACK (contact_list_action_cb)
327         },
328         { "Log", GTK_STOCK_JUSTIFY_LEFT,
329           N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
330           G_CALLBACK (contact_list_action_cb)
331         },
332 };
333
334 static guint n_entries = G_N_ELEMENTS (entries);
335
336 static const gchar *ui_info =
337         "<ui>"
338         "  <popup name='Contact'>"
339         "    <menuitem action='Chat'/>"
340         "    <menuitem action='Log'/>"
341         "    <menuitem action='SendFile'/>"
342         "    <separator/>"
343         "    <menuitem action='Invite'/>"
344         "    <separator/>"
345         "    <menuitem action='Edit'/>"
346         "    <menuitem action='Remove'/>"
347         "    <separator/>"
348         "    <menuitem action='Information'/>"
349         "  </popup>"
350         "  <popup name='Group'>"
351         "    <menuitem action='Rename'/>"
352         "  </popup>"
353         "</ui>";
354
355 enum DndDragType {
356         DND_DRAG_TYPE_CONTACT_ID,
357         DND_DRAG_TYPE_URL,
358         DND_DRAG_TYPE_STRING,
359 };
360
361 static const GtkTargetEntry drag_types_dest[] = {
362         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
363         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
364         { "text/plain",      0, DND_DRAG_TYPE_STRING },
365         { "STRING",          0, DND_DRAG_TYPE_STRING },
366 };
367
368 static const GtkTargetEntry drag_types_source[] = {
369         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
370 };
371
372 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
373 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
374
375 G_DEFINE_TYPE (GossipContactList, gossip_contact_list, GTK_TYPE_TREE_VIEW);
376
377 static void
378 gossip_contact_list_class_init (GossipContactListClass *klass)
379 {
380         GObjectClass *object_class = G_OBJECT_CLASS (klass);
381
382         object_class->finalize = contact_list_finalize;
383         object_class->get_property = contact_list_get_property;
384         object_class->set_property = contact_list_set_property;
385
386         signals[CONTACT_CHAT] =
387                 g_signal_new ("contact-chat",
388                               G_TYPE_FROM_CLASS (klass),
389                               G_SIGNAL_RUN_LAST,
390                               0,
391                               NULL, NULL,
392                               g_cclosure_marshal_VOID__OBJECT,
393                               G_TYPE_NONE,
394                               1, GOSSIP_TYPE_CONTACT);
395         signals[CONTACT_INFORMATION] =
396                 g_signal_new ("contact-information",
397                               G_TYPE_FROM_CLASS (klass),
398                               G_SIGNAL_RUN_LAST,
399                               0,
400                               NULL, NULL,
401                               g_cclosure_marshal_VOID__OBJECT,
402                               G_TYPE_NONE,
403                               1, GOSSIP_TYPE_CONTACT);
404         signals[CONTACT_EDIT] =
405                 g_signal_new ("contact-edit",
406                               G_TYPE_FROM_CLASS (klass),
407                               G_SIGNAL_RUN_LAST,
408                               0,
409                               NULL, NULL,
410                               g_cclosure_marshal_VOID__OBJECT,
411                               G_TYPE_NONE,
412                               1, GOSSIP_TYPE_CONTACT);
413         signals[CONTACT_REMOVE] =
414                 g_signal_new ("contact-remove",
415                               G_TYPE_FROM_CLASS (klass),
416                               G_SIGNAL_RUN_LAST,
417                               0,
418                               NULL, NULL,
419                               g_cclosure_marshal_VOID__OBJECT,
420                               G_TYPE_NONE,
421                               1, GOSSIP_TYPE_CONTACT);
422         signals[CONTACT_INVITE] =
423                 g_signal_new ("contact-invite",
424                               G_TYPE_FROM_CLASS (klass),
425                               G_SIGNAL_RUN_LAST,
426                               0,
427                               NULL, NULL,
428                               g_cclosure_marshal_VOID__OBJECT,
429                               G_TYPE_NONE,
430                               1, GOSSIP_TYPE_CONTACT);
431         signals[CONTACT_SEND_FILE] =
432                 g_signal_new ("contact-send-file",
433                               G_TYPE_FROM_CLASS (klass),
434                               G_SIGNAL_RUN_LAST,
435                               0,
436                               NULL, NULL,
437                               g_cclosure_marshal_VOID__OBJECT,
438                               G_TYPE_NONE,
439                               1, GOSSIP_TYPE_CONTACT);
440         signals[CONTACT_LOG] =
441                 g_signal_new ("contact-log",
442                               G_TYPE_FROM_CLASS (klass),
443                               G_SIGNAL_RUN_LAST,
444                               0,
445                               NULL, NULL,
446                               g_cclosure_marshal_VOID__OBJECT,
447                               G_TYPE_NONE,
448                               1, GOSSIP_TYPE_CONTACT);
449         signals[GROUP_RENAME] =
450                 g_signal_new ("group-rename",
451                               G_TYPE_FROM_CLASS (klass),
452                               G_SIGNAL_RUN_LAST,
453                               0,
454                               NULL, NULL,
455                               g_cclosure_marshal_VOID__STRING,
456                               G_TYPE_NONE,
457                               1, G_TYPE_STRING);
458
459
460         g_object_class_install_property (object_class,
461                                          PROP_SHOW_OFFLINE,
462                                          g_param_spec_boolean ("show-offline",
463                                                                "Show Offline",
464                                                                "Whether contact list should display "
465                                                                "offline contacts",
466                                                                FALSE,
467                                                                G_PARAM_READWRITE));
468          g_object_class_install_property (object_class,
469                                           PROP_SHOW_AVATARS,
470                                           g_param_spec_boolean ("show-avatars",
471                                                                 "Show Avatars",
472                                                                 "Whether contact list should display "
473                                                                 "avatars for contacts",
474                                                                 TRUE,
475                                                                 G_PARAM_READWRITE));
476         g_object_class_install_property (object_class,
477                                          PROP_IS_COMPACT,
478                                          g_param_spec_boolean ("is-compact",
479                                                                "Is Compact",
480                                                                "Whether the contact list is in compact mode or not",
481                                                                FALSE,
482                                                                G_PARAM_READWRITE));
483
484         g_object_class_install_property (object_class,
485                                          PROP_FILTER,
486                                          g_param_spec_string ("filter",
487                                                               "Filter",
488                                                               "The text to use to filter the contact list",
489                                                               NULL,
490                                                               G_PARAM_READWRITE));
491
492         g_type_class_add_private (object_class, sizeof (GossipContactListPriv));
493 }
494
495 static void
496 gossip_contact_list_init (GossipContactList *list)
497 {
498         GossipContactListPriv *priv;
499         GtkActionGroup        *action_group;
500         GList                 *contacts, *l;
501         GError                *error = NULL;
502
503         priv = GET_PRIV (list);
504
505         priv->manager = empathy_session_get_contact_manager ();
506         g_object_ref (priv->manager);
507         priv->is_compact = FALSE;
508         priv->show_active = TRUE;
509         priv->show_avatars = TRUE;
510
511         contact_list_create_model (list);
512         contact_list_setup_view (list);
513         empathy_contact_manager_setup (priv->manager);
514
515         /* Get saved group states. */
516         gossip_contact_groups_get_all ();
517
518         /* Set up UI Manager */
519         priv->ui = gtk_ui_manager_new ();
520
521         action_group = gtk_action_group_new ("Actions");
522         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
523         gtk_action_group_add_actions (action_group, entries, n_entries, list);
524         gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
525
526         if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
527                 g_warning ("Could not build contact menus from string:'%s'", error->message);
528                 g_error_free (error);
529         }
530
531         g_object_unref (action_group);
532
533         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (list), 
534                                               contact_list_row_separator_func,
535                                               NULL, NULL);
536
537         /* Signal connection. */
538         g_signal_connect (priv->manager,
539                           "contact-added",
540                           G_CALLBACK (contact_list_contact_added_cb),
541                           list);
542         g_signal_connect (priv->manager,
543                           "contact-removed",
544                           G_CALLBACK (contact_list_contact_removed_cb),
545                           list);
546
547         /* Connect to tree view signals rather than override. */
548         g_signal_connect (list,
549                           "button-press-event",
550                           G_CALLBACK (contact_list_button_press_event_cb),
551                           NULL);
552         g_signal_connect (list,
553                           "row-activated",
554                           G_CALLBACK (contact_list_row_activated_cb),
555                           NULL);
556         g_signal_connect (list,
557                           "row-expanded",
558                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
559                           GINT_TO_POINTER (TRUE));
560         g_signal_connect (list,
561                           "row-collapsed",
562                           G_CALLBACK (contact_list_row_expand_or_collapse_cb),
563                           GINT_TO_POINTER (FALSE));
564
565         /* Add contacts already created */
566         contacts = empathy_contact_manager_get_contacts (priv->manager);
567         for (l = contacts; l; l = l->next) {
568                 GossipContact *contact;
569
570                 contact = l->data;
571
572                 contact_list_contact_added_cb (priv->manager, contact, list);
573
574                 g_object_unref (contact);
575         }
576         g_list_free (contacts);
577 }
578
579 static void
580 contact_list_finalize (GObject *object)
581 {
582         GossipContactListPriv *priv;
583
584         priv = GET_PRIV (object);
585
586         /* FIXME: disconnect all signals on the manager and contacts */
587
588         g_object_unref (priv->manager);
589         g_object_unref (priv->ui);
590         g_object_unref (priv->store);
591         g_object_unref (priv->filter);
592         g_free (priv->filter_text);
593
594         G_OBJECT_CLASS (gossip_contact_list_parent_class)->finalize (object);
595 }
596
597 static void
598 contact_list_get_property (GObject    *object,
599                            guint       param_id,
600                            GValue     *value,
601                            GParamSpec *pspec)
602 {
603         GossipContactListPriv *priv;
604
605         priv = GET_PRIV (object);
606
607         switch (param_id) {
608         case PROP_SHOW_OFFLINE:
609                 g_value_set_boolean (value, priv->show_offline);
610                 break;
611         case PROP_SHOW_AVATARS:
612                 g_value_set_boolean (value, priv->show_avatars);
613                 break;
614         case PROP_IS_COMPACT:
615                 g_value_set_boolean (value, priv->is_compact);
616                 break;
617         case PROP_FILTER:
618                 g_value_set_string (value, priv->filter_text);
619                 break;
620         default:
621                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
622                 break;
623         };
624 }
625
626 static void
627 contact_list_set_property (GObject      *object,
628                            guint         param_id,
629                            const GValue *value,
630                            GParamSpec   *pspec)
631 {
632         GossipContactListPriv *priv;
633
634         priv = GET_PRIV (object);
635
636         switch (param_id) {
637         case PROP_SHOW_OFFLINE:
638                 gossip_contact_list_set_show_offline (GOSSIP_CONTACT_LIST (object),
639                                                       g_value_get_boolean (value));
640                 break;
641         case PROP_SHOW_AVATARS:
642                 gossip_contact_list_set_show_avatars (GOSSIP_CONTACT_LIST (object),
643                                                       g_value_get_boolean (value));
644                 break;
645         case PROP_IS_COMPACT:
646                 gossip_contact_list_set_is_compact (GOSSIP_CONTACT_LIST (object),
647                                                     g_value_get_boolean (value));
648                 break;
649         case PROP_FILTER:
650                 gossip_contact_list_set_filter (GOSSIP_CONTACT_LIST (object),
651                                                 g_value_get_string (value));
652                 break;
653         default:
654                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
655                 break;
656         };
657 }
658
659 static gboolean
660 contact_list_row_separator_func (GtkTreeModel *model,
661                                  GtkTreeIter  *iter,
662                                  gpointer      data)
663 {
664         gboolean is_separator = FALSE;
665
666         gtk_tree_model_get (model, iter,
667                             COL_IS_SEPARATOR, &is_separator,
668                             -1);
669
670         return is_separator;
671 }
672
673 static void
674 contact_list_contact_update (GossipContactList *list,
675                              GossipContact     *contact)
676 {
677         GossipContactListPriv *priv;
678         ShowActiveData        *data;
679         GtkTreeModel          *model;
680         GList                 *iters, *l;
681         gboolean               in_list;
682         gboolean               should_be_in_list;
683         gboolean               was_online = TRUE;
684         gboolean               now_online = FALSE;
685         gboolean               set_model = FALSE;
686         gboolean               do_remove = FALSE;
687         gboolean               do_set_active = FALSE;
688         gboolean               do_set_refresh = FALSE;
689         GdkPixbuf             *pixbuf_presence;
690         GdkPixbuf             *pixbuf_avatar;
691
692         priv = GET_PRIV (list);
693
694         model = GTK_TREE_MODEL (priv->store);
695
696         iters = contact_list_find_contact (list, contact);
697         if (!iters) {
698                 in_list = FALSE;
699         } else {
700                 in_list = TRUE;
701         }
702
703         /* Get online state now. */
704         now_online = gossip_contact_is_online (contact);
705
706         if (priv->show_offline || now_online) {
707                 should_be_in_list = TRUE;
708         } else {
709                 should_be_in_list = FALSE;
710         }
711
712         if (!in_list && !should_be_in_list) {
713                 /* Nothing to do. */
714                 gossip_debug (DEBUG_DOMAIN,
715                               "Contact:'%s' in list:NO, should be:NO",
716                               gossip_contact_get_name (contact));
717
718                 g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
719                 g_list_free (iters);
720                 return;
721         }
722         else if (in_list && !should_be_in_list) {
723                 gossip_debug (DEBUG_DOMAIN,
724                               "Contact:'%s' in list:YES, should be:NO",
725                               gossip_contact_get_name (contact));
726
727                 if (priv->show_active) {
728                         do_remove = TRUE;
729                         do_set_active = TRUE;
730                         do_set_refresh = TRUE;
731
732                         set_model = TRUE;
733                         gossip_debug (DEBUG_DOMAIN, "Remove item (after timeout)");
734                 } else {
735                         gossip_debug (DEBUG_DOMAIN, "Remove item (now)!");
736                         contact_list_remove_contact (list, contact);
737                 }
738         }
739         else if (!in_list && should_be_in_list) {
740                 gossip_debug (DEBUG_DOMAIN,
741                               "Contact:'%s' in list:NO, should be:YES",
742                               gossip_contact_get_name (contact));
743
744                 contact_list_add_contact (list, contact);
745
746                 if (priv->show_active) {
747                         do_set_active = TRUE;
748
749                         gossip_debug (DEBUG_DOMAIN, "Set active (contact added)");
750                 }
751         } else {
752                 gossip_debug (DEBUG_DOMAIN,
753                               "Contact:'%s' in list:YES, should be:YES",
754                               gossip_contact_get_name (contact));
755
756                 /* Get online state before. */
757                 if (iters && g_list_length (iters) > 0) {
758                         gtk_tree_model_get (model, iters->data, COL_IS_ONLINE, &was_online, -1);
759                 }
760
761                 /* Is this really an update or an online/offline. */
762                 if (priv->show_active) {
763                         if (was_online != now_online) {
764                                 gchar *str;
765
766                                 do_set_active = TRUE;
767                                 do_set_refresh = TRUE;
768
769                                 if (was_online) {
770                                         str = "online  -> offline";
771                                 } else {
772                                         str = "offline -> online";
773                                 }
774
775                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated %s)", str);
776                         } else {
777                                 /* Was TRUE for presence updates. */
778                                 /* do_set_active = FALSE;  */
779                                 do_set_refresh = TRUE;
780
781                                 gossip_debug (DEBUG_DOMAIN, "Set active (contact updated)");
782                         }
783                 }
784
785                 set_model = TRUE;
786         }
787
788         pixbuf_presence = gossip_pixbuf_for_contact (contact);
789         pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
790         for (l = iters; l && set_model; l = l->next) {
791                 gtk_tree_store_set (priv->store, l->data,
792                                     COL_PIXBUF_STATUS, pixbuf_presence,
793                                     COL_STATUS, gossip_contact_get_status (contact),
794                                     COL_IS_ONLINE, now_online,
795                                     COL_NAME, gossip_contact_get_name (contact),
796                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
797                                     -1);
798         }
799
800         if (pixbuf_presence) {
801                 g_object_unref (pixbuf_presence);
802         }
803         if (pixbuf_avatar) {
804                 g_object_unref (pixbuf_avatar);
805         }
806
807         if (priv->show_active && do_set_active) {
808                 contact_list_contact_set_active (list, contact, do_set_active, do_set_refresh);
809
810                 if (do_set_active) {
811                         data = contact_list_contact_active_new (list, contact, do_remove);
812                         g_timeout_add (ACTIVE_USER_SHOW_TIME,
813                                        (GSourceFunc) contact_list_contact_active_cb,
814                                        data);
815                 }
816         }
817
818         /* FIXME: when someone goes online then offline quickly, the
819          * first timeout sets the user to be inactive and the second
820          * timeout removes the user from the contact list, really we
821          * should remove the first timeout.
822          */
823         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
824         g_list_free (iters);
825 }
826
827 static void
828 contact_list_contact_added_cb (EmpathyContactManager *manager,
829                                GossipContact         *contact,
830                                GossipContactList     *list)
831 {
832         GossipContactListPriv *priv;
833
834         priv = GET_PRIV (list);
835
836         gossip_debug (DEBUG_DOMAIN, 
837                       "Contact:'%s' added",
838                       gossip_contact_get_name (contact));
839
840         g_signal_connect (contact, "notify::groups",
841                           G_CALLBACK (contact_list_contact_groups_updated_cb),
842                           list);
843         g_signal_connect (contact, "notify::presence",
844                           G_CALLBACK (contact_list_contact_updated_cb),
845                           list);
846         g_signal_connect (contact, "notify::name",
847                           G_CALLBACK (contact_list_contact_updated_cb),
848                           list);
849         g_signal_connect (contact, "notify::avatar",
850                           G_CALLBACK (contact_list_contact_updated_cb),
851                           list);
852         g_signal_connect (contact, "notify::type",
853                           G_CALLBACK (contact_list_contact_updated_cb),
854                           list);
855
856         contact_list_add_contact (list, contact);
857 }
858
859 static void
860 contact_list_contact_groups_updated_cb (GossipContact     *contact,
861                                         GParamSpec        *param,
862                                         GossipContactList *list)
863 {
864         GossipContactListPriv *priv;
865
866         priv = GET_PRIV (list);
867
868         if (priv->show_offline || gossip_contact_is_online (contact)) {
869         }
870
871
872         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' groups updated",
873                       gossip_contact_get_name (contact));
874
875         /* We do this to make sure the groups are correct, if not, we
876          * would have to check the groups already set up for each
877          * contact and then see what has been updated.
878          */
879         contact_list_remove_contact (list, contact);
880         contact_list_add_contact (list, contact);
881 }
882
883 static void
884 contact_list_contact_updated_cb (GossipContact     *contact,
885                                  GParamSpec        *param,
886                                  GossipContactList *list)
887 {
888         gossip_debug (DEBUG_DOMAIN,
889                       "Contact:'%s' updated, checking roster is in sync...",
890                       gossip_contact_get_name (contact));
891
892         contact_list_contact_update (list, contact);
893 }
894
895 static void
896 contact_list_contact_removed_cb (EmpathyContactManager *manager,
897                                  GossipContact         *contact,
898                                  GossipContactList     *list)
899 {
900         gossip_debug (DEBUG_DOMAIN, "Contact:'%s' removed",
901                       gossip_contact_get_name (contact));
902
903         /* Disconnect signals */
904         g_signal_handlers_disconnect_by_func (contact, 
905                                               G_CALLBACK (contact_list_contact_groups_updated_cb),
906                                               list);
907         g_signal_handlers_disconnect_by_func (contact,
908                                               G_CALLBACK (contact_list_contact_updated_cb),
909                                               list);
910
911         contact_list_remove_contact (list, contact);
912 }
913
914 static void
915 contact_list_contact_set_active (GossipContactList *list,
916                                  GossipContact     *contact,
917                                  gboolean           active,
918                                  gboolean           set_changed)
919 {
920         GossipContactListPriv *priv;
921         GtkTreeModel          *model;
922         GList                 *iters, *l;
923
924         priv = GET_PRIV (list);
925
926         model = GTK_TREE_MODEL (priv->store);
927
928         iters = contact_list_find_contact (list, contact);
929         for (l = iters; l; l = l->next) {
930                 GtkTreePath *path;
931
932                 gtk_tree_store_set (priv->store, l->data,
933                                     COL_IS_ACTIVE, active,
934                                     -1);
935
936                 gossip_debug (DEBUG_DOMAIN, "Set item %s", active ? "active" : "inactive");
937
938                 if (set_changed) {
939                         path = gtk_tree_model_get_path (model, l->data);
940                         gtk_tree_model_row_changed (model, path, l->data);
941                         gtk_tree_path_free (path);
942                 }
943         }
944
945         g_list_foreach (iters, (GFunc)gtk_tree_iter_free, NULL);
946         g_list_free (iters);
947
948 }
949
950 static ShowActiveData *
951 contact_list_contact_active_new (GossipContactList *list,
952                                  GossipContact     *contact,
953                                  gboolean           remove)
954 {
955         ShowActiveData *data;
956
957         g_return_val_if_fail (list != NULL, NULL);
958         g_return_val_if_fail (contact != NULL, NULL);
959
960         gossip_debug (DEBUG_DOMAIN, 
961                       "Contact:'%s' now active, and %s be removed",
962                       gossip_contact_get_name (contact), 
963                       remove ? "WILL" : "WILL NOT");
964         
965         data = g_slice_new0 (ShowActiveData);
966
967         data->list = g_object_ref (list);
968         data->contact = g_object_ref (contact);
969
970         data->remove = remove;
971
972         return data;
973 }
974
975 static void
976 contact_list_contact_active_free (ShowActiveData *data)
977 {
978         g_return_if_fail (data != NULL);
979
980         g_object_unref (data->contact);
981         g_object_unref (data->list);
982
983         g_slice_free (ShowActiveData, data);
984 }
985
986 static gboolean
987 contact_list_contact_active_cb (ShowActiveData *data)
988 {
989         GossipContactListPriv *priv;
990
991         g_return_val_if_fail (data != NULL, FALSE);
992
993         priv = GET_PRIV (data->list);
994
995         if (data->remove &&
996             !priv->show_offline &&
997             !gossip_contact_is_online (data->contact)) {
998                 gossip_debug (DEBUG_DOMAIN, 
999                               "Contact:'%s' active timeout, removing item",
1000                               gossip_contact_get_name (data->contact));
1001                 contact_list_remove_contact (data->list,
1002                                              data->contact);
1003         }
1004
1005         gossip_debug (DEBUG_DOMAIN, 
1006                       "Contact:'%s' no longer active",
1007                       gossip_contact_get_name (data->contact));
1008         contact_list_contact_set_active (data->list,
1009                                          data->contact,
1010                                          FALSE,
1011                                          TRUE);
1012
1013         contact_list_contact_active_free (data);
1014
1015         return FALSE;
1016 }
1017
1018 static gchar *
1019 contact_list_get_parent_group (GtkTreeModel *model,
1020                                GtkTreePath  *path,
1021                                gboolean     *path_is_group)
1022 {
1023         GtkTreeIter  parent_iter, iter;
1024         gchar       *name;
1025         gboolean     is_group;
1026
1027         g_return_val_if_fail (model != NULL, NULL);
1028         g_return_val_if_fail (path != NULL, NULL);
1029         g_return_val_if_fail (path_is_group != NULL, NULL);
1030
1031         if (!gtk_tree_model_get_iter (model, &iter, path)) {
1032                 return NULL;
1033         }
1034
1035         gtk_tree_model_get (model, &iter,
1036                             COL_IS_GROUP, &is_group,
1037                             -1);
1038
1039         if (!is_group) {
1040                 if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) {
1041                         return NULL;
1042                 }
1043
1044                 iter = parent_iter;
1045
1046                 gtk_tree_model_get (model, &iter,
1047                                     COL_IS_GROUP, &is_group,
1048                                     -1);
1049
1050                 if (!is_group) {
1051                         return NULL;
1052                 }
1053
1054                 *path_is_group = TRUE;
1055         }
1056
1057         gtk_tree_model_get (model, &iter,
1058                             COL_NAME, &name,
1059                             -1);
1060
1061         return name;
1062 }
1063
1064 static gboolean
1065 contact_list_get_group_foreach (GtkTreeModel *model,
1066                                 GtkTreePath  *path,
1067                                 GtkTreeIter  *iter,
1068                                 FindGroup    *fg)
1069 {
1070         gchar    *str;
1071         gboolean  is_group;
1072
1073         /* Groups are only at the top level. */
1074         if (gtk_tree_path_get_depth (path) != 1) {
1075                 return FALSE;
1076         }
1077
1078         gtk_tree_model_get (model, iter,
1079                             COL_NAME, &str,
1080                             COL_IS_GROUP, &is_group,
1081                             -1);
1082
1083         if (is_group && strcmp (str, fg->name) == 0) {
1084                 fg->found = TRUE;
1085                 fg->iter = *iter;
1086         }
1087
1088         g_free (str);
1089
1090         return fg->found;
1091 }
1092
1093 static void
1094 contact_list_get_group (GossipContactList *list,
1095                         const gchar       *name,
1096                         GtkTreeIter       *iter_group_to_set,
1097                         GtkTreeIter       *iter_separator_to_set,
1098                         gboolean          *created)
1099 {
1100         GossipContactListPriv *priv;
1101         GtkTreeModel          *model;
1102         GtkTreeIter            iter_group, iter_separator;
1103         FindGroup              fg;
1104
1105         priv = GET_PRIV (list);
1106
1107         memset (&fg, 0, sizeof (fg));
1108
1109         fg.name = name;
1110
1111         model = GTK_TREE_MODEL (priv->store);
1112         gtk_tree_model_foreach (model,
1113                                 (GtkTreeModelForeachFunc) contact_list_get_group_foreach,
1114                                 &fg);
1115
1116         if (!fg.found) {
1117                 if (created) {
1118                         *created = TRUE;
1119                 }
1120
1121                 gtk_tree_store_append (priv->store, &iter_group, NULL);
1122                 gtk_tree_store_set (priv->store, &iter_group,
1123                                     COL_PIXBUF_STATUS, NULL,
1124                                     COL_NAME, name,
1125                                     COL_IS_GROUP, TRUE,
1126                                     COL_IS_ACTIVE, FALSE,
1127                                     COL_IS_SEPARATOR, FALSE,
1128                                     -1);
1129
1130                 if (iter_group_to_set) {
1131                         *iter_group_to_set = iter_group;
1132                 }
1133
1134                 gtk_tree_store_append (priv->store,
1135                                        &iter_separator, 
1136                                        &iter_group);
1137                 gtk_tree_store_set (priv->store, &iter_separator,
1138                                     COL_IS_SEPARATOR, TRUE,
1139                                     -1);
1140
1141                 if (iter_separator_to_set) {
1142                         *iter_separator_to_set = iter_separator;
1143                 }
1144         } else {
1145                 if (created) {
1146                         *created = FALSE;
1147                 }
1148
1149                 if (iter_group_to_set) {
1150                         *iter_group_to_set = fg.iter;
1151                 }
1152
1153                 iter_separator = fg.iter;
1154
1155                 if (gtk_tree_model_iter_next (model, &iter_separator)) {
1156                         gboolean is_separator;
1157
1158                         gtk_tree_model_get (model, &iter_separator,
1159                                             COL_IS_SEPARATOR, &is_separator,
1160                                             -1);
1161
1162                         if (is_separator && iter_separator_to_set) {
1163                                 *iter_separator_to_set = iter_separator;
1164                         }
1165                 }
1166         }
1167 }
1168
1169 static void
1170 contact_list_add_contact (GossipContactList *list,
1171                           GossipContact     *contact)
1172 {
1173         GossipContactListPriv *priv;
1174         GtkTreeIter            iter, iter_group, iter_separator;
1175         GtkTreeModel          *model;
1176         GList                 *l, *groups;
1177
1178         priv = GET_PRIV (list);
1179         
1180         if (!priv->show_offline && !gossip_contact_is_online (contact)) {
1181                 return;
1182         }
1183
1184         model = GTK_TREE_MODEL (priv->store);
1185
1186         /* If no groups just add it at the top level. */
1187         groups = gossip_contact_get_groups (contact);
1188         if (!groups) {
1189                 GdkPixbuf *pixbuf_status;
1190                 GdkPixbuf *pixbuf_avatar;
1191                 gboolean   show_avatar = FALSE;
1192
1193                 pixbuf_status = gossip_pixbuf_for_contact (contact);
1194                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1195
1196                 if (priv->show_avatars && !priv->is_compact) {
1197                         show_avatar = TRUE;
1198                 }
1199
1200                 gossip_debug (DEBUG_DOMAIN, "");
1201                 gossip_debug (DEBUG_DOMAIN, 
1202                               "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1203
1204                 gossip_debug (DEBUG_DOMAIN, 
1205                               "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #1",
1206                               contact,
1207                               G_IS_OBJECT (contact) ? "yes" : "no",
1208                               GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1209
1210                 gtk_tree_store_append (priv->store, &iter, NULL);
1211                 gtk_tree_store_set (priv->store, &iter,
1212                                     COL_PIXBUF_STATUS, pixbuf_status,
1213                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
1214                                     COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1215                                     COL_NAME, gossip_contact_get_name (contact),
1216                                     COL_STATUS, gossip_contact_get_status (contact),
1217                                     COL_STATUS_VISIBLE, !priv->is_compact,
1218                                     COL_CONTACT, contact,
1219                                     COL_IS_GROUP, FALSE,
1220                                     COL_IS_ACTIVE, FALSE,
1221                                     COL_IS_ONLINE, gossip_contact_is_online (contact),
1222                                     COL_IS_SEPARATOR, FALSE,
1223                                     -1);
1224
1225                 gossip_debug (DEBUG_DOMAIN, 
1226                               "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
1227                 gossip_debug (DEBUG_DOMAIN, "");
1228
1229                 if (pixbuf_avatar) {
1230                         g_object_unref (pixbuf_avatar);
1231                 }
1232                 if (pixbuf_status) {
1233                         g_object_unref (pixbuf_status);
1234                 }
1235         }
1236
1237         /* Else add to each group. */
1238         for (l = groups; l; l = l->next) {
1239                 GtkTreePath *path;
1240                 GtkTreeIter  model_iter_group;
1241                 GdkPixbuf   *pixbuf_status;
1242                 GdkPixbuf   *pixbuf_avatar;
1243                 const gchar *name;
1244                 gboolean     created;
1245                 gboolean     found;
1246                 gboolean     show_avatar = FALSE;
1247
1248                 name = l->data;
1249                 if (!name) {
1250                         continue;
1251                 }
1252
1253                 pixbuf_status = gossip_pixbuf_for_contact (contact);
1254                 pixbuf_avatar = gossip_pixbuf_avatar_from_contact_scaled (contact, 32, 32);
1255
1256                 contact_list_get_group (list, name, &iter_group, &iter_separator, &created);
1257
1258                 if (priv->show_avatars && !priv->is_compact) {
1259                         show_avatar = TRUE;
1260                 }
1261
1262                 gossip_debug (DEBUG_DOMAIN, "");
1263                 gossip_debug (DEBUG_DOMAIN, 
1264                               "vvvvvvvvvvvvvvvv FIXME: Errors may follow below (since filter work) vvvvvvvvvvvvvvvv");
1265
1266                 gossip_debug (DEBUG_DOMAIN, 
1267                               "**** GossipContact:%p, is GObject:%s, is GossipContact:%s, ADDING CONTACT #2",
1268                               contact,
1269                               G_IS_OBJECT (contact) ? "yes" : "no",
1270                               GOSSIP_IS_CONTACT (contact) ? "yes" : "no");
1271
1272                 gtk_tree_store_insert_after (priv->store, &iter, &iter_group, NULL);
1273                 gtk_tree_store_set (priv->store, &iter,
1274                                     COL_PIXBUF_STATUS, pixbuf_status,
1275                                     COL_PIXBUF_AVATAR, pixbuf_avatar,
1276                                     COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
1277                                     COL_NAME, gossip_contact_get_name (contact),
1278                                     COL_STATUS, gossip_contact_get_status (contact),
1279                                     COL_STATUS_VISIBLE, !priv->is_compact,
1280                                     COL_CONTACT, contact,
1281                                     COL_IS_GROUP, FALSE,
1282                                     COL_IS_ACTIVE, FALSE,
1283                                     COL_IS_ONLINE, gossip_contact_is_online (contact),
1284                                     COL_IS_SEPARATOR, FALSE,
1285                                     -1);
1286
1287                 gossip_debug (DEBUG_DOMAIN, 
1288                               "^^^^^^^^^^^^^^^^ FIXME: Errors may occur above  (since filter work) ^^^^^^^^^^^^^^^^");
1289                 gossip_debug (DEBUG_DOMAIN, "");
1290
1291                 if (pixbuf_avatar) {
1292                         g_object_unref (pixbuf_avatar);
1293                 }
1294                 if (pixbuf_status) {
1295                         g_object_unref (pixbuf_status);
1296                 }
1297
1298                 if (!created) {
1299                         continue;
1300                 }
1301
1302                 found = gtk_tree_model_filter_convert_child_iter_to_iter (GTK_TREE_MODEL_FILTER (priv->filter),  
1303                                                                           &model_iter_group,  
1304                                                                           &iter_group); 
1305                 if (!found) {
1306                         continue;
1307                 }
1308                 
1309                 path = gtk_tree_model_get_path (model, &model_iter_group);
1310                 if (!path) {
1311                         continue;
1312                 }
1313
1314                 if (gossip_contact_group_get_expanded (name)) {
1315                         g_signal_handlers_block_by_func (list,
1316                                                          contact_list_row_expand_or_collapse_cb,
1317                                                          GINT_TO_POINTER (TRUE));
1318                         gtk_tree_view_expand_row (GTK_TREE_VIEW (list), path, TRUE);
1319                         g_signal_handlers_unblock_by_func (list,
1320                                                            contact_list_row_expand_or_collapse_cb,
1321                                                            GINT_TO_POINTER (TRUE));
1322                 } else {
1323                         g_signal_handlers_block_by_func (list,
1324                                                          contact_list_row_expand_or_collapse_cb,
1325                                                          GINT_TO_POINTER (FALSE));
1326                         gtk_tree_view_collapse_row (GTK_TREE_VIEW (list), path);
1327                         g_signal_handlers_unblock_by_func (list,
1328                                                            contact_list_row_expand_or_collapse_cb,
1329                                                            GINT_TO_POINTER (FALSE));
1330                 }
1331
1332                 gtk_tree_path_free (path);
1333         }
1334 }
1335
1336 static void
1337 contact_list_remove_contact (GossipContactList *list,
1338                              GossipContact     *contact)
1339 {
1340         GossipContactListPriv *priv;
1341         GtkTreeModel          *model;
1342         GList                 *iters, *l;
1343
1344         priv = GET_PRIV (list);
1345
1346         iters = contact_list_find_contact (list, contact);
1347         if (!iters) {
1348                 return;
1349         }
1350         
1351         /* Clean up model */
1352         model = GTK_TREE_MODEL (priv->store);
1353
1354         for (l = iters; l; l = l->next) {
1355                 GtkTreeIter parent;
1356
1357                 /* NOTE: it is only <= 2 here because we have
1358                  * separators after the group name, otherwise it
1359                  * should be 1. 
1360                  */
1361                 if (gtk_tree_model_iter_parent (model, &parent, l->data) &&
1362                     gtk_tree_model_iter_n_children (model, &parent) <= 2) {
1363                         gtk_tree_store_remove (priv->store, &parent);
1364                 } else {
1365                         gtk_tree_store_remove (priv->store, l->data);
1366                 }
1367         }
1368
1369         g_list_foreach (iters, (GFunc) gtk_tree_iter_free, NULL);
1370         g_list_free (iters);
1371 }
1372
1373 static void
1374 contact_list_create_model (GossipContactList *list)
1375 {
1376         GossipContactListPriv *priv;
1377         GtkTreeModel          *model;
1378         
1379         priv = GET_PRIV (list);
1380
1381         if (priv->store) {
1382                 g_object_unref (priv->store);
1383         }
1384
1385         if (priv->filter) {
1386                 g_object_unref (priv->filter);
1387         }
1388
1389         priv->store = gtk_tree_store_new (COL_COUNT,
1390                                           GDK_TYPE_PIXBUF,     /* Status pixbuf */
1391                                           GDK_TYPE_PIXBUF,     /* Avatar pixbuf */
1392                                           G_TYPE_BOOLEAN,      /* Avatar pixbuf visible */
1393                                           G_TYPE_STRING,       /* Name */
1394                                           G_TYPE_STRING,       /* Status string */
1395                                           G_TYPE_BOOLEAN,      /* Show status */
1396                                           GOSSIP_TYPE_CONTACT, /* Contact type */
1397                                           G_TYPE_BOOLEAN,      /* Is group */
1398                                           G_TYPE_BOOLEAN,      /* Is active */
1399                                           G_TYPE_BOOLEAN,      /* Is online */
1400                                           G_TYPE_BOOLEAN);     /* Is separator */
1401
1402         /* Save normal model */
1403         model = GTK_TREE_MODEL (priv->store);
1404
1405         /* Set up sorting */
1406         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (model),
1407                                          COL_NAME,
1408                                          contact_list_sort_func,
1409                                          list, NULL);
1410
1411         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
1412                                               COL_NAME,
1413                                               GTK_SORT_ASCENDING);
1414
1415         /* Create filter */
1416         priv->filter = gtk_tree_model_filter_new (model, NULL);
1417
1418         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
1419                                                 (GtkTreeModelFilterVisibleFunc)
1420                                                 contact_list_filter_func,
1421                                                 list, NULL);
1422
1423         gtk_tree_view_set_model (GTK_TREE_VIEW (list), priv->filter);
1424 }
1425
1426 static gboolean
1427 contact_list_search_equal_func (GtkTreeModel *model,
1428                                 gint          column,
1429                                 const gchar  *key,
1430                                 GtkTreeIter  *iter,
1431                                 gpointer      search_data)
1432 {
1433         gchar    *name, *name_folded;
1434         gchar    *key_folded;
1435         gboolean  ret;
1436
1437         if (!key) {
1438                 return FALSE;
1439         }
1440
1441         gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1442
1443         if (!name) {
1444                 return FALSE;
1445         }
1446
1447         name_folded = g_utf8_casefold (name, -1);
1448         key_folded = g_utf8_casefold (key, -1);
1449
1450         if (name_folded && key_folded && 
1451             strstr (name_folded, key_folded)) {
1452                 ret = FALSE;
1453         } else {
1454                 ret = TRUE;
1455         }
1456
1457         g_free (name);
1458         g_free (name_folded);
1459         g_free (key_folded);
1460
1461         return ret;
1462 }
1463
1464 static void
1465 contact_list_setup_view (GossipContactList *list)
1466 {
1467         GtkCellRenderer   *cell;
1468         GtkTreeViewColumn *col;
1469         gint               i;
1470
1471         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (list),
1472                                              contact_list_search_equal_func,
1473                                              list,
1474                                              NULL);
1475
1476         g_object_set (list,
1477                       "headers-visible", FALSE,
1478                       "reorderable", TRUE,
1479                       "show-expanders", FALSE,
1480                       NULL);
1481
1482         col = gtk_tree_view_column_new ();
1483
1484         /* State */
1485         cell = gtk_cell_renderer_pixbuf_new ();
1486         gtk_tree_view_column_pack_start (col, cell, FALSE);
1487         gtk_tree_view_column_set_cell_data_func (
1488                 col, cell,
1489                 (GtkTreeCellDataFunc) contact_list_pixbuf_cell_data_func,
1490                 list, NULL);
1491
1492         g_object_set (cell,
1493                       "xpad", 5,
1494                       "ypad", 1,
1495                       "visible", FALSE,
1496                       NULL);
1497
1498         /* Name */
1499         cell = gossip_cell_renderer_text_new ();
1500         gtk_tree_view_column_pack_start (col, cell, TRUE);
1501         gtk_tree_view_column_set_cell_data_func (
1502                 col, cell,
1503                 (GtkTreeCellDataFunc) contact_list_text_cell_data_func,
1504                 list, NULL);
1505
1506         gtk_tree_view_column_add_attribute (col, cell,
1507                                             "name", COL_NAME);
1508         gtk_tree_view_column_add_attribute (col, cell,
1509                                             "status", COL_STATUS);
1510         gtk_tree_view_column_add_attribute (col, cell,
1511                                             "is_group", COL_IS_GROUP);
1512
1513         /* Avatar */
1514         cell = gtk_cell_renderer_pixbuf_new ();
1515         gtk_tree_view_column_pack_start (col, cell, FALSE);
1516         gtk_tree_view_column_set_cell_data_func (
1517                 col, cell,
1518                 (GtkTreeCellDataFunc) contact_list_avatar_cell_data_func,
1519                 list, NULL);
1520
1521         g_object_set (cell,
1522                       "xpad", 0,
1523                       "ypad", 0,
1524                       "visible", FALSE,
1525                       "width", 32,
1526                       "height", 32,
1527                       NULL);
1528
1529         /* Expander */
1530         cell = gossip_cell_renderer_expander_new ();
1531         gtk_tree_view_column_pack_end (col, cell, FALSE);
1532         gtk_tree_view_column_set_cell_data_func (
1533                 col, cell,
1534                 (GtkTreeCellDataFunc) contact_list_expander_cell_data_func,
1535                 list, NULL);
1536
1537         /* Actually add the column now we have added all cell renderers */
1538         gtk_tree_view_append_column (GTK_TREE_VIEW (list), col);
1539
1540         /* Drag & Drop. */
1541         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1542                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1543                                                       FALSE);
1544         }
1545
1546         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1547                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1548                                                         FALSE);
1549         }
1550
1551         /* Note: We support the COPY action too, but need to make the
1552          * MOVE action the default.
1553          */
1554         gtk_drag_source_set (GTK_WIDGET (list),
1555                              GDK_BUTTON1_MASK,
1556                              drag_types_source,
1557                              G_N_ELEMENTS (drag_types_source),
1558                              GDK_ACTION_MOVE);
1559
1560         gtk_drag_dest_set (GTK_WIDGET (list),
1561                            GTK_DEST_DEFAULT_ALL,
1562                            drag_types_dest,
1563                            G_N_ELEMENTS (drag_types_dest),
1564                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
1565
1566         g_signal_connect (GTK_WIDGET (list),
1567                           "drag-data-received",
1568                           G_CALLBACK (contact_list_drag_data_received),
1569                           NULL);
1570
1571         /* FIXME: noticed but when you drag the row over the treeview
1572          * fast, it seems to stop redrawing itself, if we don't
1573          * connect this signal, all is fine.
1574          */
1575         g_signal_connect (GTK_WIDGET (list),
1576                           "drag-motion",
1577                           G_CALLBACK (contact_list_drag_motion),
1578                           NULL);
1579
1580         g_signal_connect (GTK_WIDGET (list),
1581                           "drag-begin",
1582                           G_CALLBACK (contact_list_drag_begin),
1583                           NULL);
1584         g_signal_connect (GTK_WIDGET (list),
1585                           "drag-data-get",
1586                           G_CALLBACK (contact_list_drag_data_get),
1587                           NULL);
1588         g_signal_connect (GTK_WIDGET (list),
1589                           "drag-end",
1590                           G_CALLBACK (contact_list_drag_end),
1591                           NULL);
1592 }
1593
1594 static void
1595 contact_list_drag_data_received (GtkWidget         *widget,
1596                                  GdkDragContext    *context,
1597                                  gint               x,
1598                                  gint               y,
1599                                  GtkSelectionData  *selection,
1600                                  guint              info,
1601                                  guint              time,
1602                                  gpointer           user_data)
1603 {
1604         GossipContactListPriv   *priv;
1605         GtkTreeModel            *model;
1606         GtkTreePath             *path;
1607         GtkTreeViewDropPosition  position;
1608         GossipContact           *contact;
1609         GList                   *groups;
1610         const gchar             *id;
1611         gchar                   *old_group;
1612         gboolean                 is_row;
1613         gboolean                 drag_success = TRUE;
1614         gboolean                 drag_del = FALSE;
1615
1616         priv = GET_PRIV (widget);
1617
1618         id = (const gchar*) selection->data;
1619         gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
1620                       context->action == GDK_ACTION_MOVE ? "move" : "",
1621                       context->action == GDK_ACTION_COPY ? "copy" : "",
1622                       id);
1623
1624         /* FIXME: This is ambigous, an id can come from multiple accounts */
1625         contact = empathy_contact_manager_find (priv->manager, id);
1626         if (!contact) {
1627                 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
1628                 return;
1629         }
1630
1631         groups = gossip_contact_get_groups (contact);
1632
1633         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
1634                                                     x,
1635                                                     y,
1636                                                     &path,
1637                                                     &position);
1638
1639         if (!is_row) {
1640                 if (g_list_length (groups) != 1) {
1641                         /* if they have dragged a contact out of a
1642                          * group then we would set the contact to have
1643                          * NO groups but only if they were ONE group
1644                          * to begin with - should we do this
1645                          * regardless to how many groups they are in
1646                          * already or not at all?
1647                          */
1648                         return;
1649                 }
1650
1651                 gossip_contact_set_groups (contact, NULL);
1652         } else {
1653                 GList    *l, *new_groups;
1654                 gchar    *name;
1655                 gboolean  is_group;
1656
1657                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1658                 name = contact_list_get_parent_group (model, path, &is_group);
1659
1660                 if (groups && name &&
1661                     g_list_find_custom (groups, name, (GCompareFunc)strcmp)) {
1662                         g_free (name);
1663                         return;
1664                 }
1665
1666                 /* Get source group information. */
1667                 priv = GET_PRIV (widget);
1668                 if (!priv->drag_row) {
1669                         g_free (name);
1670                         return;
1671                 }
1672
1673                 path = gtk_tree_row_reference_get_path (priv->drag_row);
1674                 if (!path) {
1675                         g_free (name);
1676                         return;
1677                 }
1678
1679                 old_group = contact_list_get_parent_group (model, path, &is_group);
1680                 gtk_tree_path_free (path);
1681
1682                 if (!name && old_group && GDK_ACTION_MOVE) {
1683                         drag_success = FALSE;
1684                 }
1685
1686                 if (context->action == GDK_ACTION_MOVE) {
1687                         drag_del = TRUE;
1688                 }
1689
1690                 /* Create new groups GList. */
1691                 for (l = groups, new_groups = NULL; l && drag_success; l = l->next) {
1692                         gchar *str;
1693
1694                         str = l->data;
1695                         if (context->action == GDK_ACTION_MOVE &&
1696                             old_group != NULL &&
1697                             strcmp (str, old_group) == 0) {
1698                                 continue;
1699                         }
1700
1701                         if (str == NULL) {
1702                                 continue;
1703                         }
1704
1705                         new_groups = g_list_append (new_groups, g_strdup (str));
1706                 }
1707
1708                 if (drag_success) {
1709                         if (name) {
1710                                 new_groups = g_list_append (new_groups, name);
1711                         }
1712                         gossip_contact_set_groups (contact, new_groups);
1713                 } else {
1714                         g_free (name);
1715                 }
1716         }
1717
1718         gtk_drag_finish (context, drag_success, drag_del, GDK_CURRENT_TIME);
1719 }
1720
1721 static gboolean
1722 contact_list_drag_motion (GtkWidget      *widget,
1723                           GdkDragContext *context,
1724                           gint            x,
1725                           gint            y,
1726                           guint           time,
1727                           gpointer        data)
1728 {
1729         static DragMotionData *dm = NULL;
1730         GtkTreePath           *path;
1731         gboolean               is_row;
1732         gboolean               is_different = FALSE;
1733         gboolean               cleanup = TRUE;
1734
1735         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
1736                                                 x,
1737                                                 y,
1738                                                 &path,
1739                                                 NULL,
1740                                                 NULL,
1741                                                 NULL);
1742
1743         cleanup &= (!dm);
1744
1745         if (is_row) {
1746                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
1747                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
1748         } else {
1749                 cleanup &= FALSE;
1750         }
1751
1752         if (!is_different && !cleanup) {
1753                 return TRUE;
1754         }
1755
1756         if (dm) {
1757                 gtk_tree_path_free (dm->path);
1758                 if (dm->timeout_id) {
1759                         g_source_remove (dm->timeout_id);
1760                 }
1761
1762                 g_free (dm);
1763
1764                 dm = NULL;
1765         }
1766
1767         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
1768                 dm = g_new0 (DragMotionData, 1);
1769
1770                 dm->list = GOSSIP_CONTACT_LIST (widget);
1771                 dm->path = gtk_tree_path_copy (path);
1772
1773                 dm->timeout_id = g_timeout_add (
1774                         1500,
1775                         (GSourceFunc) contact_list_drag_motion_cb,
1776                         dm);
1777         }
1778
1779         return TRUE;
1780 }
1781
1782 static gboolean
1783 contact_list_drag_motion_cb (DragMotionData *data)
1784 {
1785         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->list),
1786                                   data->path,
1787                                   FALSE);
1788
1789         data->timeout_id = 0;
1790
1791         return FALSE;
1792 }
1793
1794 static void
1795 contact_list_drag_begin (GtkWidget      *widget,
1796                          GdkDragContext *context,
1797                          gpointer        user_data)
1798 {
1799         GossipContactListPriv *priv;
1800         GtkTreeSelection      *selection;
1801         GtkTreeModel          *model;
1802         GtkTreePath           *path;
1803         GtkTreeIter            iter;
1804
1805         priv = GET_PRIV (widget);
1806
1807         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1808         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1809                 return;
1810         }
1811
1812         path = gtk_tree_model_get_path (model, &iter);
1813         priv->drag_row = gtk_tree_row_reference_new (model, path);
1814         gtk_tree_path_free (path);
1815 }
1816
1817 static void
1818 contact_list_drag_data_get (GtkWidget             *widget,
1819                             GdkDragContext        *context,
1820                             GtkSelectionData      *selection,
1821                             guint                  info,
1822                             guint                  time,
1823                             gpointer               user_data)
1824 {
1825         GossipContactListPriv *priv;
1826         GtkTreePath           *src_path;
1827         GtkTreeIter            iter;
1828         GtkTreeModel          *model;
1829         GossipContact         *contact;
1830         const gchar           *id;
1831
1832         priv = GET_PRIV (widget);
1833
1834         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
1835         if (!priv->drag_row) {
1836                 return;
1837         }
1838
1839         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1840         if (!src_path) {
1841                 return;
1842         }
1843
1844         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1845                 gtk_tree_path_free (src_path);
1846                 return;
1847         }
1848
1849         gtk_tree_path_free (src_path);
1850
1851         contact = gossip_contact_list_get_selected (GOSSIP_CONTACT_LIST (widget));
1852         if (!contact) {
1853                 return;
1854         }
1855
1856         id = gossip_contact_get_id (contact);
1857         g_object_unref (contact);
1858
1859         switch (info) {
1860         case DND_DRAG_TYPE_CONTACT_ID:
1861                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1862                                         (guchar*)id, strlen (id) + 1);
1863                 break;
1864
1865         default:
1866                 return;
1867         }
1868 }
1869
1870 static void
1871 contact_list_drag_end (GtkWidget      *widget,
1872                        GdkDragContext *context,
1873                        gpointer        user_data)
1874 {
1875         GossipContactListPriv *priv;
1876
1877         priv = GET_PRIV (widget);
1878
1879         if (priv->drag_row) {
1880                 gtk_tree_row_reference_free (priv->drag_row);
1881                 priv->drag_row = NULL;
1882         }
1883 }
1884
1885 static void
1886 contact_list_cell_set_background (GossipContactList  *list,
1887                                   GtkCellRenderer    *cell,
1888                                   gboolean            is_group,
1889                                   gboolean            is_active)
1890 {
1891         GdkColor  color;
1892         GtkStyle *style;
1893
1894         g_return_if_fail (list != NULL);
1895         g_return_if_fail (cell != NULL);
1896
1897         style = gtk_widget_get_style (GTK_WIDGET (list));
1898
1899         if (!is_group) {
1900                 if (is_active) {
1901                         color = style->bg[GTK_STATE_SELECTED];
1902
1903                         /* Here we take the current theme colour and add it to
1904                          * the colour for white and average the two. This
1905                          * gives a colour which is inline with the theme but
1906                          * slightly whiter.
1907                          */
1908                         color.red = (color.red + (style->white).red) / 2;
1909                         color.green = (color.green + (style->white).green) / 2;
1910                         color.blue = (color.blue + (style->white).blue) / 2;
1911
1912                         g_object_set (cell,
1913                                       "cell-background-gdk", &color,
1914                                       NULL);
1915                 } else {
1916                         g_object_set (cell,
1917                                       "cell-background-gdk", NULL,
1918                                       NULL);
1919                 }
1920         } else {
1921                 g_object_set (cell,
1922                               "cell-background-gdk", NULL,
1923                               NULL);
1924 #if 0
1925                 gint color_sum_normal;
1926                 gint color_sum_selected;
1927                 
1928                 color = style->base[GTK_STATE_SELECTED];
1929                 color_sum_normal = color.red+color.green+color.blue;
1930                 color = style->base[GTK_STATE_NORMAL];
1931                 color_sum_selected = color.red+color.green+color.blue;
1932                 color = style->text_aa[GTK_STATE_INSENSITIVE];
1933
1934                 if (color_sum_normal < color_sum_selected) { 
1935                         /* Found a light theme */
1936                         color.red = (color.red + (style->white).red) / 2;
1937                         color.green = (color.green + (style->white).green) / 2;
1938                         color.blue = (color.blue + (style->white).blue) / 2;
1939                 } else { 
1940                         /* Found a dark theme */
1941                         color.red = (color.red + (style->black).red) / 2;
1942                         color.green = (color.green + (style->black).green) / 2;
1943                         color.blue = (color.blue + (style->black).blue) / 2;
1944                 }
1945
1946                 g_object_set (cell,
1947                               "cell-background-gdk", &color,
1948                               NULL);
1949 #endif
1950         }
1951 }
1952
1953 static void
1954 contact_list_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
1955                                     GtkCellRenderer   *cell,
1956                                     GtkTreeModel      *model,
1957                                     GtkTreeIter       *iter,
1958                                     GossipContactList *list)
1959 {
1960         GdkPixbuf *pixbuf;
1961         gboolean   is_group;
1962         gboolean   is_active;
1963
1964         gtk_tree_model_get (model, iter,
1965                             COL_IS_GROUP, &is_group,
1966                             COL_IS_ACTIVE, &is_active,
1967                             COL_PIXBUF_STATUS, &pixbuf,
1968                             -1);
1969
1970         g_object_set (cell,
1971                       "visible", !is_group,
1972                       "pixbuf", pixbuf,
1973                       NULL);
1974
1975         if (pixbuf) {
1976                 g_object_unref (pixbuf);
1977         }
1978
1979         contact_list_cell_set_background (list, cell, is_group, is_active);
1980 }
1981
1982 static void
1983 contact_list_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1984                                     GtkCellRenderer   *cell,
1985                                     GtkTreeModel      *model,
1986                                     GtkTreeIter       *iter,
1987                                     GossipContactList *list)
1988 {
1989         GdkPixbuf *pixbuf;
1990         gboolean   show_avatar;
1991         gboolean   is_group;
1992         gboolean   is_active;
1993
1994         gtk_tree_model_get (model, iter,
1995                             COL_PIXBUF_AVATAR, &pixbuf,
1996                             COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1997                             COL_IS_GROUP, &is_group,
1998                             COL_IS_ACTIVE, &is_active,
1999                             -1);
2000
2001         g_object_set (cell,
2002                       "visible", !is_group && show_avatar,
2003                       "pixbuf", pixbuf,
2004                       NULL);
2005
2006         if (pixbuf) {
2007                 g_object_unref (pixbuf);
2008         }
2009
2010         contact_list_cell_set_background (list, cell, is_group, is_active);
2011 }
2012
2013 static void
2014 contact_list_text_cell_data_func (GtkTreeViewColumn *tree_column,
2015                                   GtkCellRenderer   *cell,
2016                                   GtkTreeModel      *model,
2017                                   GtkTreeIter       *iter,
2018                                   GossipContactList *list)
2019 {
2020         gboolean is_group;
2021         gboolean is_active;
2022         gboolean show_status;
2023
2024         gtk_tree_model_get (model, iter,
2025                             COL_IS_GROUP, &is_group,
2026                             COL_IS_ACTIVE, &is_active,
2027                             COL_STATUS_VISIBLE, &show_status,
2028                             -1);
2029
2030         g_object_set (cell,
2031                       "show-status", show_status,
2032                       NULL);
2033
2034         contact_list_cell_set_background (list, cell, is_group, is_active);
2035 }
2036
2037 static void
2038 contact_list_expander_cell_data_func (GtkTreeViewColumn *column,
2039                                       GtkCellRenderer   *cell,
2040                                       GtkTreeModel      *model,
2041                                       GtkTreeIter       *iter,
2042                                       GossipContactList *list)
2043 {
2044         gboolean is_group;
2045         gboolean is_active;
2046
2047         gtk_tree_model_get (model, iter,
2048                             COL_IS_GROUP, &is_group,
2049                             COL_IS_ACTIVE, &is_active,
2050                             -1);
2051
2052         if (gtk_tree_model_iter_has_child (model, iter)) {
2053                 GtkTreePath *path;
2054                 gboolean     row_expanded;
2055
2056                 path = gtk_tree_model_get_path (model, iter);
2057                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
2058                 gtk_tree_path_free (path);
2059
2060                 g_object_set (cell,
2061                               "visible", TRUE,
2062                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
2063                               NULL);
2064         } else {
2065                 g_object_set (cell, "visible", FALSE, NULL);
2066         }
2067
2068         contact_list_cell_set_background (list, cell, is_group, is_active);
2069 }
2070
2071 static GtkWidget *
2072 contact_list_get_contact_menu (GossipContactList *list,
2073                                gboolean           can_send_file,
2074                                gboolean           can_show_log)
2075 {
2076         GossipContactListPriv *priv;
2077         GtkAction             *action;
2078         GtkWidget             *widget;
2079
2080         priv = GET_PRIV (list);
2081
2082         /* Sort out sensitive items */
2083         action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
2084         gtk_action_set_sensitive (action, can_show_log);
2085
2086         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
2087         gtk_action_set_visible (action, can_send_file);
2088
2089         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
2090
2091         return widget;
2092 }
2093
2094 GtkWidget *
2095 gossip_contact_list_get_group_menu (GossipContactList *list)
2096 {
2097         GossipContactListPriv *priv;
2098         GtkWidget             *widget;
2099
2100         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2101
2102         priv = GET_PRIV (list);
2103
2104         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
2105
2106         return widget;
2107 }
2108
2109 GtkWidget *
2110 gossip_contact_list_get_contact_menu (GossipContactList *list,
2111                                       GossipContact     *contact)
2112 {
2113         GtkWidget *menu;
2114         gboolean   can_show_log;
2115         gboolean   can_send_file;
2116
2117         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2118         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
2119
2120         can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
2121         can_send_file = FALSE;
2122
2123         menu = contact_list_get_contact_menu (list,
2124                                               can_send_file,
2125                                               can_show_log);
2126         return menu;
2127 }
2128
2129 static gboolean
2130 contact_list_button_press_event_cb (GossipContactList *list,
2131                                     GdkEventButton    *event,
2132                                     gpointer           user_data)
2133 {
2134         GossipContactListPriv *priv;
2135         GossipContact         *contact;
2136         GtkTreePath           *path;
2137         GtkTreeSelection      *selection;
2138         GtkTreeModel          *model;
2139         GtkTreeIter            iter;
2140         gboolean               row_exists;
2141         GtkWidget             *menu;
2142
2143         if (event->button != 3) {
2144                 return FALSE;
2145         }
2146
2147         priv = GET_PRIV (list);
2148
2149         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2150         model = GTK_TREE_MODEL (priv->store);
2151
2152         gtk_widget_grab_focus (GTK_WIDGET (list));
2153
2154         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (list),
2155                                                     event->x, event->y,
2156                                                     &path,
2157                                                     NULL, NULL, NULL);
2158         if (!row_exists) {
2159                 return FALSE;
2160         }
2161
2162         gtk_tree_selection_unselect_all (selection);
2163         gtk_tree_selection_select_path (selection, path);
2164
2165         gtk_tree_model_get_iter (model, &iter, path);
2166         gtk_tree_path_free (path);
2167
2168         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2169
2170         if (contact) {
2171                 menu = gossip_contact_list_get_contact_menu (list, contact);
2172                 g_object_unref (contact);
2173         } else {
2174                 menu = gossip_contact_list_get_group_menu (list);
2175         }
2176
2177         if (!menu) {
2178                 return FALSE;
2179         }
2180
2181         gtk_widget_show (menu);
2182
2183         gtk_menu_popup (GTK_MENU (menu),
2184                         NULL, NULL, NULL, NULL,
2185                         event->button, event->time);
2186
2187         return TRUE;
2188 }
2189
2190 static void
2191 contact_list_row_activated_cb (GossipContactList *list,
2192                                GtkTreePath       *path,
2193                                GtkTreeViewColumn *col,
2194                                gpointer           user_data)
2195 {
2196         GossipContact *contact;
2197         GtkTreeView   *view;
2198         GtkTreeModel  *model;
2199         GtkTreeIter    iter;
2200
2201         view = GTK_TREE_VIEW (list);
2202         model = gtk_tree_view_get_model (view);
2203
2204         gtk_tree_model_get_iter (model, &iter, path);
2205         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2206
2207         if (contact) {
2208                 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2209                 g_object_unref (contact);
2210         }
2211 }
2212
2213 static void
2214 contact_list_row_expand_or_collapse_cb (GossipContactList *list,
2215                                         GtkTreeIter       *iter,
2216                                         GtkTreePath       *path,
2217                                         gpointer           user_data)
2218 {
2219         GtkTreeModel *model;
2220         gchar        *name;
2221         gboolean      expanded;
2222
2223         model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
2224
2225         gtk_tree_model_get (model, iter,
2226                             COL_NAME, &name,
2227                             -1);
2228
2229         expanded = GPOINTER_TO_INT (user_data);
2230         gossip_contact_group_set_expanded (name, expanded);
2231
2232         g_free (name);
2233 }
2234
2235 static gint
2236 contact_list_sort_func (GtkTreeModel *model,
2237                         GtkTreeIter  *iter_a,
2238                         GtkTreeIter  *iter_b,
2239                         gpointer      user_data)
2240 {
2241         gchar         *name_a, *name_b;
2242         GossipContact *contact_a, *contact_b;
2243         gboolean       is_separator_a, is_separator_b;
2244         gint           ret_val;
2245
2246         gtk_tree_model_get (model, iter_a,
2247                             COL_NAME, &name_a,
2248                             COL_CONTACT, &contact_a,
2249                             COL_IS_SEPARATOR, &is_separator_a,
2250                             -1);
2251         gtk_tree_model_get (model, iter_b,
2252                             COL_NAME, &name_b,
2253                             COL_CONTACT, &contact_b,
2254                             COL_IS_SEPARATOR, &is_separator_b,
2255                             -1);
2256
2257         /* If contact is NULL it means it's a group. */
2258
2259         if (is_separator_a || is_separator_b) {
2260                 if (is_separator_a) {
2261                         ret_val = -1;
2262                 } else if (is_separator_b) {
2263                         ret_val = 1;
2264                 }
2265         } else if (!contact_a && contact_b) {
2266                 ret_val = 1;
2267         } else if (contact_a && !contact_b) {
2268                 ret_val = -1;
2269         } else {
2270                 ret_val = g_utf8_collate (name_a, name_b);
2271         }
2272
2273         g_free (name_a);
2274         g_free (name_b);
2275
2276         if (contact_a) {
2277                 g_object_unref (contact_a);
2278         }
2279
2280         if (contact_b) {
2281                 g_object_unref (contact_b);
2282         }
2283
2284         return ret_val;
2285 }
2286
2287 static gboolean 
2288 contact_list_filter_show_contact (GossipContact *contact,
2289                                   const gchar   *filter)
2290 {
2291         gchar    *str;
2292         gboolean  visible;
2293
2294         /* Check contact id */
2295         str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
2296         visible = G_STR_EMPTY (str) || strstr (str, filter);
2297         g_free (str);
2298
2299         if (visible) {
2300                 return TRUE;
2301         }
2302
2303         /* Check contact name */
2304         str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
2305         visible = G_STR_EMPTY (str) || strstr (str, filter);
2306         g_free (str);
2307         
2308         return visible;
2309 }
2310
2311 static gboolean
2312 contact_list_filter_show_group (GossipContactList *list,
2313                                 const gchar       *group,
2314                                 const gchar       *filter)
2315 {
2316         GossipContactListPriv *priv;
2317         GList                 *contacts, *l;
2318         gchar                 *str;
2319         gboolean               show_group = FALSE;
2320
2321         priv = GET_PRIV (list);
2322         
2323         str = g_utf8_casefold (group, -1);
2324         if (!str) {
2325                 return FALSE;
2326         }
2327
2328         /* If the filter is the partially the group name, we show the
2329          * whole group.
2330          */
2331         if (strstr (str, filter)) {
2332                 g_free (str);
2333                 return TRUE;
2334         }
2335
2336         /* At this point, we need to check in advance if this
2337          * group should be shown because a contact we want to
2338          * show exists in it.
2339          */
2340         contacts = empathy_contact_manager_get_contacts (priv->manager);
2341         for (l = contacts; l && !show_group; l = l->next) {
2342                 if (!gossip_contact_is_in_group (l->data, group)) {
2343                         continue;
2344                 }
2345
2346                 if (contact_list_filter_show_contact (l->data, filter)) {
2347                         show_group = TRUE;
2348                 }
2349         }
2350         g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
2351         g_list_free (contacts);
2352         g_free (str);
2353
2354         return show_group;
2355 }
2356
2357 static gboolean
2358 contact_list_filter_func (GtkTreeModel      *model,
2359                           GtkTreeIter       *iter,
2360                           GossipContactList *list)
2361 {
2362         GossipContactListPriv *priv;
2363         gboolean               is_group;
2364         gboolean               is_separator;
2365         gboolean               visible = TRUE;
2366
2367         priv = GET_PRIV (list);
2368
2369         if (G_STR_EMPTY (priv->filter_text)) {
2370                 return TRUE;
2371         }
2372         
2373         /* Check to see if iter matches any group names */
2374         gtk_tree_model_get (model, iter,
2375                             COL_IS_GROUP, &is_group,
2376                             COL_IS_SEPARATOR, &is_separator,
2377                             -1);
2378
2379         if (is_group) {
2380                 gchar *name;
2381
2382                 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
2383                 visible &= contact_list_filter_show_group (list, 
2384                                                            name, 
2385                                                            priv->filter_text);
2386                 g_free (name);
2387         } else if (is_separator) {
2388                 /* Do nothing here */
2389         } else {
2390                 GossipContact *contact;
2391
2392                 /* Check contact id */
2393                 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
2394                 visible &= contact_list_filter_show_contact (contact, 
2395                                                              priv->filter_text);
2396                 g_object_unref (contact);
2397         }
2398
2399         return visible;
2400 }
2401
2402 static gboolean
2403 contact_list_iter_equal_contact (GtkTreeModel  *model,
2404                                  GtkTreeIter   *iter,
2405                                  GossipContact *contact)
2406 {
2407         GossipContact *c;
2408         gboolean       equal;
2409
2410         gtk_tree_model_get (model, iter,
2411                             COL_CONTACT, &c,
2412                             -1);
2413
2414         if (!c) {
2415                 return FALSE;
2416         }
2417
2418         equal = (c == contact);
2419         g_object_unref (c);
2420
2421         return equal;
2422 }
2423
2424 static gboolean
2425 contact_list_find_contact_foreach (GtkTreeModel *model,
2426                                    GtkTreePath  *path,
2427                                    GtkTreeIter  *iter,
2428                                    FindContact  *fc)
2429 {
2430         if (contact_list_iter_equal_contact (model, iter, fc->contact)) {
2431                 fc->found = TRUE;
2432                 fc->iters = g_list_append (fc->iters, gtk_tree_iter_copy (iter));
2433         }
2434
2435         /* We want to find ALL contacts that match, this means if we
2436          * have the same contact in 3 groups, all iters should be
2437          * returned.
2438          */
2439         return FALSE;
2440 }
2441
2442 static GList *
2443 contact_list_find_contact (GossipContactList *list,
2444                            GossipContact     *contact)
2445 {
2446         GossipContactListPriv *priv;
2447         GtkTreeModel          *model;
2448         GList                 *l = NULL;
2449         FindContact            fc;
2450
2451         priv = GET_PRIV (list);
2452
2453         memset (&fc, 0, sizeof (fc));
2454
2455         fc.contact = contact;
2456
2457         model = GTK_TREE_MODEL (priv->store);
2458         gtk_tree_model_foreach (model,
2459                                 (GtkTreeModelForeachFunc) contact_list_find_contact_foreach,
2460                                 &fc);
2461
2462         if (fc.found) {
2463                 l = fc.iters;
2464         }
2465
2466         return l;
2467 }
2468
2469 static void
2470 contact_list_action_cb (GtkAction         *action,
2471                         GossipContactList *list)
2472 {
2473         GossipContact *contact;
2474         const gchar   *name;
2475         gchar         *group;
2476
2477         name = gtk_action_get_name (action);
2478         if (!name) {
2479                 return;
2480         }
2481
2482         gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
2483
2484         contact = gossip_contact_list_get_selected (list);
2485         group = gossip_contact_list_get_selected_group (list);
2486
2487         if (contact && strcmp (name, "Chat") == 0) {
2488                 g_signal_emit (list, signals[CONTACT_CHAT], 0, contact);
2489         }
2490         else if (contact && strcmp (name, "Information") == 0) {
2491                 g_signal_emit (list, signals[CONTACT_INFORMATION], 0, contact);
2492         }
2493         else if (contact && strcmp (name, "Edit") == 0) {
2494                 g_signal_emit (list, signals[CONTACT_EDIT], 0, contact);
2495         }
2496         else if (contact && strcmp (name, "Remove") == 0) {
2497                 g_signal_emit (list, signals[CONTACT_REMOVE], 0, contact);
2498         }
2499         else if (contact && strcmp (name, "Invite") == 0) {
2500                 g_signal_emit (list, signals[CONTACT_INVITE], 0, contact);
2501         }
2502         else if (contact && strcmp (name, "SendFile") == 0) {
2503                 g_signal_emit (list, signals[CONTACT_SEND_FILE], 0, contact);
2504         }
2505         else if (contact && strcmp (name, "Log") == 0) {
2506                 g_signal_emit (list, signals[CONTACT_LOG], 0, contact);
2507         }
2508         else if (group && strcmp (name, "Rename") == 0) {
2509                 g_signal_emit (list, signals[GROUP_RENAME], 0, group);
2510         }
2511
2512         g_free (group);
2513         if (contact) {
2514                 g_object_unref (contact);
2515         }
2516 }
2517
2518 static gboolean
2519 contact_list_update_list_mode_foreach (GtkTreeModel      *model,
2520                                        GtkTreePath       *path,
2521                                        GtkTreeIter       *iter,
2522                                        GossipContactList *list)
2523 {
2524         GossipContactListPriv *priv;
2525         gboolean               show_avatar = FALSE;
2526
2527         priv = GET_PRIV (list);
2528
2529         if (priv->show_avatars && !priv->is_compact) {
2530                 show_avatar = TRUE;
2531         }
2532
2533         gtk_tree_store_set (priv->store, iter,
2534                             COL_PIXBUF_AVATAR_VISIBLE, show_avatar,
2535                             COL_STATUS_VISIBLE, !priv->is_compact,
2536                             -1);
2537
2538         return FALSE;
2539 }
2540
2541 GossipContactList *
2542 gossip_contact_list_new (void)
2543 {
2544         return g_object_new (GOSSIP_TYPE_CONTACT_LIST, NULL);
2545 }
2546
2547 GossipContact *
2548 gossip_contact_list_get_selected (GossipContactList *list)
2549 {
2550         GossipContactListPriv *priv;
2551         GtkTreeSelection      *selection;
2552         GtkTreeIter            iter;
2553         GtkTreeModel          *model;
2554         GossipContact         *contact;
2555
2556         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2557
2558         priv = GET_PRIV (list);
2559
2560         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2561         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2562                 return NULL;
2563         }
2564
2565         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
2566
2567         return contact;
2568 }
2569
2570 gchar *
2571 gossip_contact_list_get_selected_group (GossipContactList *list)
2572 {
2573         GossipContactListPriv *priv;
2574         GtkTreeSelection      *selection;
2575         GtkTreeIter            iter;
2576         GtkTreeModel          *model;
2577         gboolean               is_group;
2578         gchar                 *name;
2579
2580         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), NULL);
2581
2582         priv = GET_PRIV (list);
2583
2584         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
2585         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
2586                 return NULL;
2587         }
2588
2589         gtk_tree_model_get (model, &iter,
2590                             COL_IS_GROUP, &is_group,
2591                             COL_NAME, &name,
2592                             -1);
2593
2594         if (!is_group) {
2595                 g_free (name);
2596                 return NULL;
2597         }
2598
2599         return name;
2600 }
2601
2602 gboolean
2603 gossip_contact_list_get_show_offline (GossipContactList *list)
2604 {
2605         GossipContactListPriv *priv;
2606
2607         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), FALSE);
2608
2609         priv = GET_PRIV (list);
2610
2611         return priv->show_offline;
2612 }
2613
2614 gboolean
2615 gossip_contact_list_get_show_avatars (GossipContactList *list)
2616 {
2617         GossipContactListPriv *priv;
2618
2619         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2620
2621         priv = GET_PRIV (list);
2622
2623         return priv->show_avatars;
2624 }
2625
2626 gboolean
2627 gossip_contact_list_get_is_compact (GossipContactList *list)
2628 {
2629         GossipContactListPriv *priv;
2630
2631         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST (list), TRUE);
2632
2633         priv = GET_PRIV (list);
2634
2635         return priv->is_compact;
2636 }
2637
2638 void
2639 gossip_contact_list_set_show_offline (GossipContactList *list,
2640                                       gboolean           show_offline)
2641 {
2642         GossipContactListPriv *priv;
2643         GList                 *contacts, *l;
2644         gboolean               show_active;
2645
2646         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2647
2648         priv = GET_PRIV (list);
2649
2650         priv->show_offline = show_offline;
2651         show_active = priv->show_active;
2652
2653         /* Disable temporarily. */
2654         priv->show_active = FALSE;
2655
2656         contacts = empathy_contact_manager_get_contacts (priv->manager);
2657         for (l = contacts; l; l = l->next) {
2658                 GossipContact *contact;
2659
2660                 contact = GOSSIP_CONTACT (l->data);
2661
2662                 contact_list_contact_update (list, contact);
2663                 
2664                 g_object_unref (contact);
2665         }
2666         g_list_free (contacts);
2667
2668         /* Restore to original setting. */
2669         priv->show_active = show_active;
2670 }
2671
2672 void
2673 gossip_contact_list_set_show_avatars (GossipContactList *list,
2674                                       gboolean           show_avatars)
2675 {
2676         GossipContactListPriv *priv;
2677         GtkTreeModel          *model;
2678
2679         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2680
2681         priv = GET_PRIV (list);
2682
2683         priv->show_avatars = show_avatars;
2684
2685         model = GTK_TREE_MODEL (priv->store);
2686
2687         gtk_tree_model_foreach (model,
2688                                 (GtkTreeModelForeachFunc)
2689                                 contact_list_update_list_mode_foreach,
2690                                 list);
2691 }
2692
2693 void
2694 gossip_contact_list_set_is_compact (GossipContactList *list,
2695                                     gboolean           is_compact)
2696 {
2697         GossipContactListPriv *priv;
2698         GtkTreeModel          *model;
2699
2700         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2701
2702         priv = GET_PRIV (list);
2703
2704         priv->is_compact = is_compact;
2705
2706         model = GTK_TREE_MODEL (priv->store);
2707
2708         gtk_tree_model_foreach (model,
2709                                 (GtkTreeModelForeachFunc)
2710                                 contact_list_update_list_mode_foreach,
2711                                 list);
2712 }
2713
2714 void
2715 gossip_contact_list_set_filter (GossipContactList *list,
2716                                 const gchar       *filter)
2717 {
2718         GossipContactListPriv *priv;
2719
2720         g_return_if_fail (GOSSIP_IS_CONTACT_LIST (list));
2721
2722         priv = GET_PRIV (list);
2723
2724         g_free (priv->filter_text);
2725         if (filter) {
2726                 priv->filter_text = g_utf8_casefold (filter, -1);
2727         } else {
2728                 priv->filter_text = NULL;
2729         }
2730
2731         gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
2732         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
2733 }