]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-contact-list-view.c
Correctly remember the contact who sent last message.
[empathy.git] / libempathy-gtk / gossip-contact-list-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  */
25
26 #include "config.h"
27
28 #include <string.h>
29
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 #include <glade/glade.h>
33
34 #include <libmissioncontrol/mc-account.h>
35 #include <libmissioncontrol/mission-control.h>
36
37 #include <libempathy/empathy-contact-manager.h>
38 #include <libempathy/empathy-contact-list.h>
39 #include <libempathy/gossip-debug.h>
40 #include <libempathy/gossip-utils.h>
41 #include <libempathy/empathy-marshal.h>
42
43 #include "gossip-contact-list-view.h"
44 #include "gossip-contact-list-store.h"
45 #include "empathy-images.h"
46 #include "gossip-contact-groups.h"
47 #include "gossip-cell-renderer-expander.h"
48 #include "gossip-cell-renderer-text.h"
49 #include "gossip-ui-utils.h"
50 //#include "gossip-chat-invite.h"
51 //#include "gossip-contact-info-dialog.h"
52 //#include "gossip-edit-contact-dialog.h"
53 //#include "gossip-ft-window.h"
54 //#include "gossip-log-window.h"
55
56 #define DEBUG_DOMAIN "ContactListView"
57
58 /* Flashing delay for icons (milliseconds). */
59 #define FLASH_TIMEOUT 500
60
61 /* Active users are those which have recently changed state
62  * (e.g. online, offline or from normal to a busy state).
63  */
64
65 #define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CONTACT_LIST_VIEW, GossipContactListViewPriv))
66
67 struct _GossipContactListViewPriv {
68         GossipContactListStore *store;
69         GtkUIManager           *ui;
70         GtkTreeRowReference    *drag_row;
71         GtkTreeModel           *filter;
72         gchar                  *filter_text;
73
74         GossipContactListViewDragReceivedFunc drag_received;
75         gpointer                              drag_received_data;
76 };
77
78 typedef struct {
79         GossipContactListView *view;
80         GtkTreePath           *path;
81         guint                  timeout_id;
82 } DragMotionData;
83
84 typedef struct {
85         GossipContactListView *view;
86         GossipContact         *contact;
87         gboolean               remove;
88 } ShowActiveData;
89
90 static void        gossip_contact_list_view_class_init         (GossipContactListViewClass *klass);
91 static void        gossip_contact_list_view_init               (GossipContactListView      *list);
92 static void        contact_list_view_finalize                  (GObject                    *object);
93 static void        contact_list_view_get_property              (GObject                    *object,
94                                                                 guint                       param_id,
95                                                                 GValue                     *value,
96                                                                 GParamSpec                 *pspec);
97 static void        contact_list_view_set_property              (GObject                    *object,
98                                                                 guint                       param_id,
99                                                                 const GValue               *value,
100                                                                 GParamSpec                 *pspec);
101 static void        contact_list_view_setup                     (GossipContactListView      *view);
102 static void        contact_list_view_row_has_child_toggled_cb  (GtkTreeModel               *model,
103                                                                 GtkTreePath                *path,
104                                                                 GtkTreeIter                *iter,
105                                                                 GossipContactListView      *view);
106 static void        contact_list_view_contact_received          (GossipContactListView      *view,
107                                                                 GossipContact              *contact,
108                                                                 GdkDragAction               action,
109                                                                 const gchar                *old_group,
110                                                                 const gchar                *new_group);
111 static void        contact_list_view_drag_data_received        (GtkWidget                  *widget,
112                                                                 GdkDragContext             *context,
113                                                                 gint                        x,
114                                                                 gint                        y,
115                                                                 GtkSelectionData           *selection,
116                                                                 guint                       info,
117                                                                 guint                       time,
118                                                                 gpointer                    user_data);
119 static gboolean    contact_list_view_drag_motion               (GtkWidget                  *widget,
120                                                                 GdkDragContext             *context,
121                                                                 gint                        x,
122                                                                 gint                        y,
123                                                                 guint                       time,
124                                                                 gpointer                    data);
125 static gboolean    contact_list_view_drag_motion_cb            (DragMotionData             *data);
126 static void        contact_list_view_drag_begin                (GtkWidget                  *widget,
127                                                                 GdkDragContext             *context,
128                                                                 gpointer                    user_data);
129 static void        contact_list_view_drag_data_get             (GtkWidget                  *widget,
130                                                                 GdkDragContext             *context,
131                                                                 GtkSelectionData           *selection,
132                                                                 guint                       info,
133                                                                 guint                       time,
134                                                                 gpointer                    user_data);
135 static void        contact_list_view_drag_end                  (GtkWidget                  *widget,
136                                                                 GdkDragContext             *context,
137                                                                 gpointer                    user_data);
138 static void        contact_list_view_cell_set_background       (GossipContactListView      *view,
139                                                                 GtkCellRenderer            *cell,
140                                                                 gboolean                    is_group,
141                                                                 gboolean                    is_active);
142 static void        contact_list_view_pixbuf_cell_data_func     (GtkTreeViewColumn          *tree_column,
143                                                                 GtkCellRenderer            *cell,
144                                                                 GtkTreeModel               *model,
145                                                                 GtkTreeIter                *iter,
146                                                                 GossipContactListView      *view);
147 static void        contact_list_view_avatar_cell_data_func     (GtkTreeViewColumn          *tree_column,
148                                                                 GtkCellRenderer            *cell,
149                                                                 GtkTreeModel               *model,
150                                                                 GtkTreeIter                *iter,
151                                                                 GossipContactListView      *view);
152 static void        contact_list_view_text_cell_data_func       (GtkTreeViewColumn          *tree_column,
153                                                                 GtkCellRenderer            *cell,
154                                                                 GtkTreeModel               *model,
155                                                                 GtkTreeIter                *iter,
156                                                                 GossipContactListView      *view);
157 static void        contact_list_view_expander_cell_data_func   (GtkTreeViewColumn          *column,
158                                                                 GtkCellRenderer            *cell,
159                                                                 GtkTreeModel               *model,
160                                                                 GtkTreeIter                *iter,
161                                                                 GossipContactListView      *view);
162 static GtkWidget * contact_list_view_get_contact_menu          (GossipContactListView      *view,
163                                                                 gboolean                    can_send_file,
164                                                                 gboolean                    can_show_log);
165 static gboolean    contact_list_view_button_press_event_cb     (GossipContactListView      *view,
166                                                                 GdkEventButton             *event,
167                                                                 gpointer                    user_data);
168 static void        contact_list_view_row_activated_cb          (GossipContactListView      *view,
169                                                                 GtkTreePath                *path,
170                                                                 GtkTreeViewColumn          *col,
171                                                                 gpointer                    user_data);
172 static void        contact_list_view_row_expand_or_collapse_cb (GossipContactListView      *view,
173                                                                 GtkTreeIter                *iter,
174                                                                 GtkTreePath                *path,
175                                                                 gpointer                    user_data);
176 static gboolean    contact_list_view_filter_show_contact       (GossipContact              *contact,
177                                                                 const gchar                *filter);
178 static gboolean    contact_list_view_filter_show_group         (GossipContactListView      *view,
179                                                                 const gchar                *group,
180                                                                 const gchar                *filter);
181 static gboolean    contact_list_view_filter_func               (GtkTreeModel               *model,
182                                                                 GtkTreeIter                *iter,
183                                                                 GossipContactListView      *view);
184 static void        contact_list_view_action_cb                 (GtkAction                  *action,
185                                                                 GossipContactListView      *view);
186 static void        contact_list_view_action_activated          (GossipContactListView      *view,
187                                                                 GossipContact              *contact);
188
189 enum {
190         PROP_0,
191         PROP_FILTER,
192 };
193
194 static const GtkActionEntry entries[] = {
195         { "ContactMenu", NULL,
196           N_("_Contact"), NULL, NULL,
197           NULL
198         },
199         { "GroupMenu", NULL,
200           N_("_Group"),NULL, NULL,
201           NULL
202         },
203         { "Chat", EMPATHY_IMAGE_MESSAGE,
204           N_("_Chat"), NULL, N_("Chat with contact"),
205           G_CALLBACK (contact_list_view_action_cb)
206         },
207         { "Information", EMPATHY_IMAGE_CONTACT_INFORMATION,
208           N_("Infor_mation"), "<control>I", N_("View contact information"),
209           G_CALLBACK (contact_list_view_action_cb)
210         },
211         { "Rename", NULL,
212           N_("Re_name"), NULL, N_("Rename"),
213           G_CALLBACK (contact_list_view_action_cb)
214         },
215         { "Edit", GTK_STOCK_EDIT,
216           N_("_Edit"), NULL, N_("Edit the groups and name for this contact"),
217           G_CALLBACK (contact_list_view_action_cb)
218         },
219         { "Remove", GTK_STOCK_REMOVE,
220           N_("_Remove"), NULL, N_("Remove contact"),
221           G_CALLBACK (contact_list_view_action_cb)
222         },
223         { "Invite", EMPATHY_IMAGE_GROUP_MESSAGE,
224           N_("_Invite to Chat Room"), NULL, N_("Invite to a currently open chat room"),
225           G_CALLBACK (contact_list_view_action_cb)
226         },
227         { "SendFile", NULL,
228           N_("_Send File..."), NULL, N_("Send a file"),
229           G_CALLBACK (contact_list_view_action_cb)
230         },
231         { "Log", GTK_STOCK_JUSTIFY_LEFT,
232           N_("_View Previous Conversations"), NULL, N_("View previous conversations with this contact"),
233           G_CALLBACK (contact_list_view_action_cb)
234         },
235 };
236
237 static guint n_entries = G_N_ELEMENTS (entries);
238
239 static const gchar *ui_info =
240         "<ui>"
241         "  <popup name='Contact'>"
242         "    <menuitem action='Chat'/>"
243         "    <menuitem action='Log'/>"
244         "    <menuitem action='SendFile'/>"
245         "    <separator/>"
246         "    <menuitem action='Invite'/>"
247         "    <separator/>"
248         "    <menuitem action='Edit'/>"
249         "    <menuitem action='Remove'/>"
250         "    <separator/>"
251         "    <menuitem action='Information'/>"
252         "  </popup>"
253         "  <popup name='Group'>"
254         "    <menuitem action='Rename'/>"
255         "  </popup>"
256         "</ui>";
257
258 enum DndDragType {
259         DND_DRAG_TYPE_CONTACT_ID,
260         DND_DRAG_TYPE_URL,
261         DND_DRAG_TYPE_STRING,
262 };
263
264 static const GtkTargetEntry drag_types_dest[] = {
265         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
266         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
267         { "text/plain",      0, DND_DRAG_TYPE_STRING },
268         { "STRING",          0, DND_DRAG_TYPE_STRING },
269 };
270
271 static const GtkTargetEntry drag_types_source[] = {
272         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
273 };
274
275 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
276 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
277
278 enum {
279         DRAG_CONTACT_RECEIVED,
280         LAST_SIGNAL
281 };
282
283 static guint signals[LAST_SIGNAL];
284
285 G_DEFINE_TYPE (GossipContactListView, gossip_contact_list_view, GTK_TYPE_TREE_VIEW);
286
287 static void
288 gossip_contact_list_view_class_init (GossipContactListViewClass *klass)
289 {
290         GObjectClass *object_class = G_OBJECT_CLASS (klass);
291
292         object_class->finalize = contact_list_view_finalize;
293         object_class->get_property = contact_list_view_get_property;
294         object_class->set_property = contact_list_view_set_property;
295
296         signals[DRAG_CONTACT_RECEIVED] =
297                 g_signal_new ("drag-contact-received",
298                               G_OBJECT_CLASS_TYPE (klass),
299                               G_SIGNAL_RUN_LAST,
300                               0,
301                               NULL, NULL,
302                               empathy_marshal_VOID__OBJECT_STRING_STRING,
303                               G_TYPE_NONE,
304                               3, GOSSIP_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
305
306         g_object_class_install_property (object_class,
307                                          PROP_FILTER,
308                                          g_param_spec_string ("filter",
309                                                               "Filter",
310                                                               "The text to use to filter the contact list",
311                                                               NULL,
312                                                               G_PARAM_READWRITE));
313
314         g_type_class_add_private (object_class, sizeof (GossipContactListViewPriv));
315 }
316
317 static void
318 gossip_contact_list_view_init (GossipContactListView *view)
319 {
320         GossipContactListViewPriv *priv;
321         GtkActionGroup            *action_group;
322         GError                    *error = NULL;
323
324         priv = GET_PRIV (view);
325
326         /* Get saved group states. */
327         gossip_contact_groups_get_all ();
328
329         /* Set up UI Manager */
330         priv->ui = gtk_ui_manager_new ();
331
332         action_group = gtk_action_group_new ("Actions");
333         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
334         gtk_action_group_add_actions (action_group, entries, n_entries, view);
335         gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
336
337         if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
338                 g_warning ("Could not build contact menus from string:'%s'", error->message);
339                 g_error_free (error);
340         }
341
342         g_object_unref (action_group);
343
344         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
345                                               gossip_contact_list_store_row_separator_func,
346                                               NULL, NULL);
347
348         /* Connect to tree view signals rather than override. */
349         g_signal_connect (view,
350                           "button-press-event",
351                           G_CALLBACK (contact_list_view_button_press_event_cb),
352                           NULL);
353         g_signal_connect (view,
354                           "row-activated",
355                           G_CALLBACK (contact_list_view_row_activated_cb),
356                           NULL);
357         g_signal_connect (view,
358                           "row-expanded",
359                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
360                           GINT_TO_POINTER (TRUE));
361         g_signal_connect (view,
362                           "row-collapsed",
363                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
364                           GINT_TO_POINTER (FALSE));
365 }
366
367 static void
368 contact_list_view_finalize (GObject *object)
369 {
370         GossipContactListViewPriv *priv;
371
372         priv = GET_PRIV (object);
373
374         if (priv->ui) {
375                 g_object_unref (priv->ui);
376         }
377         if (priv->store) {
378                 g_object_unref (priv->store);
379         }
380         if (priv->filter) {
381                 g_object_unref (priv->filter);
382         }
383         g_free (priv->filter_text);
384
385         G_OBJECT_CLASS (gossip_contact_list_view_parent_class)->finalize (object);
386 }
387
388 static void
389 contact_list_view_get_property (GObject    *object,
390                                 guint       param_id,
391                                 GValue     *value,
392                                 GParamSpec *pspec)
393 {
394         GossipContactListViewPriv *priv;
395
396         priv = GET_PRIV (object);
397
398         switch (param_id) {
399         case PROP_FILTER:
400                 g_value_set_string (value, priv->filter_text);
401                 break;
402         default:
403                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
404                 break;
405         };
406 }
407
408 static void
409 contact_list_view_set_property (GObject      *object,
410                                 guint         param_id,
411                                 const GValue *value,
412                                 GParamSpec   *pspec)
413 {
414         GossipContactListViewPriv *priv;
415
416         priv = GET_PRIV (object);
417
418         switch (param_id) {
419         case PROP_FILTER:
420                 gossip_contact_list_view_set_filter (GOSSIP_CONTACT_LIST_VIEW (object),
421                                                 g_value_get_string (value));
422                 break;
423         default:
424                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
425                 break;
426         };
427 }
428
429 GossipContactListView *
430 gossip_contact_list_view_new (GossipContactListStore *store)
431 {
432         GossipContactListViewPriv *priv;
433         GossipContactListView     *view;
434         
435         view = g_object_new (GOSSIP_TYPE_CONTACT_LIST_VIEW, NULL);
436         priv = GET_PRIV (view);
437
438         priv->store = g_object_ref (store);
439         contact_list_view_setup (view);
440
441         return view;
442 }
443
444 GossipContact *
445 gossip_contact_list_view_get_selected (GossipContactListView *view)
446 {
447         GossipContactListViewPriv *priv;
448         GtkTreeSelection          *selection;
449         GtkTreeIter                iter;
450         GtkTreeModel              *model;
451         GossipContact             *contact;
452
453         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
454
455         priv = GET_PRIV (view);
456
457         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
458         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
459                 return NULL;
460         }
461
462         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
463
464         return contact;
465 }
466
467 gchar *
468 gossip_contact_list_view_get_selected_group (GossipContactListView *view)
469 {
470         GossipContactListViewPriv *priv;
471         GtkTreeSelection          *selection;
472         GtkTreeIter                iter;
473         GtkTreeModel              *model;
474         gboolean                   is_group;
475         gchar                     *name;
476
477         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
478
479         priv = GET_PRIV (view);
480
481         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
482         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
483                 return NULL;
484         }
485
486         gtk_tree_model_get (model, &iter,
487                             COL_IS_GROUP, &is_group,
488                             COL_NAME, &name,
489                             -1);
490
491         if (!is_group) {
492                 g_free (name);
493                 return NULL;
494         }
495
496         return name;
497 }
498
499 GtkWidget *
500 gossip_contact_list_view_get_group_menu (GossipContactListView *view)
501 {
502         GossipContactListViewPriv *priv;
503         GtkWidget                 *widget;
504
505         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
506
507         priv = GET_PRIV (view);
508
509         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
510
511         return widget;
512 }
513
514 GtkWidget *
515 gossip_contact_list_view_get_contact_menu (GossipContactListView *view,
516                                            GossipContact         *contact)
517 {
518         gboolean can_show_log;
519         gboolean can_send_file;
520
521         g_return_val_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view), NULL);
522         g_return_val_if_fail (GOSSIP_IS_CONTACT (contact), NULL);
523
524         can_show_log = FALSE; /* FIXME: gossip_log_exists_for_contact (contact); */
525         can_send_file = FALSE;
526
527         return contact_list_view_get_contact_menu (view,
528                                                    can_send_file,
529                                                    can_show_log);
530 }
531
532 void
533 gossip_contact_list_view_set_filter (GossipContactListView *view,
534                                      const gchar           *filter)
535 {
536         GossipContactListViewPriv *priv;
537
538         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
539
540         priv = GET_PRIV (view);
541
542         g_free (priv->filter_text);
543         if (filter) {
544                 priv->filter_text = g_utf8_casefold (filter, -1);
545         } else {
546                 priv->filter_text = NULL;
547         }
548
549         gossip_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
550         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
551 }
552
553 void
554 gossip_contact_list_view_set_drag_received_func (GossipContactListView                 *view,
555                                                  GossipContactListViewDragReceivedFunc  func,
556                                                  gpointer                               user_data)
557 {
558         GossipContactListViewPriv *priv;
559
560         g_return_if_fail (GOSSIP_IS_CONTACT_LIST_VIEW (view));
561
562         priv = GET_PRIV (view);
563
564         if (func) {
565                 priv->drag_received = func;
566                 priv->drag_received_data = user_data;
567         } else {
568                 priv->drag_received = NULL;
569                 priv->drag_received_data = NULL;
570         }
571 }
572
573 static void
574 contact_list_view_setup (GossipContactListView *view)
575 {
576         GossipContactListViewPriv *priv;
577         GtkCellRenderer           *cell;
578         GtkTreeViewColumn         *col;
579         gint                       i;
580
581         priv = GET_PRIV (view);
582
583         /* Create filter */
584         priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL);
585
586         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
587                                                 (GtkTreeModelFilterVisibleFunc)
588                                                 contact_list_view_filter_func,
589                                                 view, NULL);
590         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
591                                              gossip_contact_list_store_search_equal_func,
592                                              view, NULL);
593         g_signal_connect (priv->filter, "row-has-child-toggled",
594                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
595                           view);
596         gtk_tree_view_set_model (GTK_TREE_VIEW (view), priv->filter);
597
598
599         /* Setup view */
600         g_object_set (view,
601                       "headers-visible", FALSE,
602                       "reorderable", TRUE,
603                       "show-expanders", FALSE,
604                       NULL);
605
606         col = gtk_tree_view_column_new ();
607
608         /* State */
609         cell = gtk_cell_renderer_pixbuf_new ();
610         gtk_tree_view_column_pack_start (col, cell, FALSE);
611         gtk_tree_view_column_set_cell_data_func (
612                 col, cell,
613                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
614                 view, NULL);
615
616         g_object_set (cell,
617                       "xpad", 5,
618                       "ypad", 1,
619                       "visible", FALSE,
620                       NULL);
621
622         /* Name */
623         cell = gossip_cell_renderer_text_new ();
624         gtk_tree_view_column_pack_start (col, cell, TRUE);
625         gtk_tree_view_column_set_cell_data_func (
626                 col, cell,
627                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
628                 view, NULL);
629
630         gtk_tree_view_column_add_attribute (col, cell,
631                                             "name", COL_NAME);
632         gtk_tree_view_column_add_attribute (col, cell,
633                                             "status", COL_STATUS);
634         gtk_tree_view_column_add_attribute (col, cell,
635                                             "is_group", COL_IS_GROUP);
636
637         /* Avatar */
638         cell = gtk_cell_renderer_pixbuf_new ();
639         gtk_tree_view_column_pack_start (col, cell, FALSE);
640         gtk_tree_view_column_set_cell_data_func (
641                 col, cell,
642                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
643                 view, NULL);
644
645         g_object_set (cell,
646                       "xpad", 0,
647                       "ypad", 0,
648                       "visible", FALSE,
649                       "width", 32,
650                       "height", 32,
651                       NULL);
652
653         /* Expander */
654         cell = gossip_cell_renderer_expander_new ();
655         gtk_tree_view_column_pack_end (col, cell, FALSE);
656         gtk_tree_view_column_set_cell_data_func (
657                 col, cell,
658                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
659                 view, NULL);
660
661         /* Actually add the column now we have added all cell renderers */
662         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
663
664         /* Drag & Drop. */
665         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
666                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
667                                                       FALSE);
668         }
669
670         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
671                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
672                                                         FALSE);
673         }
674
675         /* Note: We support the COPY action too, but need to make the
676          * MOVE action the default.
677          */
678         gtk_drag_source_set (GTK_WIDGET (view),
679                              GDK_BUTTON1_MASK,
680                              drag_types_source,
681                              G_N_ELEMENTS (drag_types_source),
682                              GDK_ACTION_MOVE);
683
684         gtk_drag_dest_set (GTK_WIDGET (view),
685                            GTK_DEST_DEFAULT_ALL,
686                            drag_types_dest,
687                            G_N_ELEMENTS (drag_types_dest),
688                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
689
690         g_signal_connect (GTK_WIDGET (view),
691                           "drag-data-received",
692                           G_CALLBACK (contact_list_view_drag_data_received),
693                           NULL);
694
695         /* FIXME: noticed but when you drag the row over the treeview
696          * fast, it seems to stop redrawing itself, if we don't
697          * connect this signal, all is fine.
698          */
699         g_signal_connect (view,
700                           "drag-motion",
701                           G_CALLBACK (contact_list_view_drag_motion),
702                           NULL);
703
704         g_signal_connect (view,
705                           "drag-begin",
706                           G_CALLBACK (contact_list_view_drag_begin),
707                           NULL);
708         g_signal_connect (view,
709                           "drag-data-get",
710                           G_CALLBACK (contact_list_view_drag_data_get),
711                           NULL);
712         g_signal_connect (view,
713                           "drag-end",
714                           G_CALLBACK (contact_list_view_drag_end),
715                           NULL);
716 }
717
718 static void
719 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
720                                             GtkTreePath           *path,
721                                             GtkTreeIter           *iter,
722                                             GossipContactListView *view)
723 {
724         gboolean  is_group = FALSE;
725         gchar    *name = NULL;
726
727         gtk_tree_model_get (model, iter,
728                             COL_IS_GROUP, &is_group,
729                             COL_NAME, &name,
730                             -1);
731
732         if (!is_group || G_STR_EMPTY (name)) {
733                 g_free (name);
734                 return;
735         }
736
737         if (gossip_contact_group_get_expanded (name)) {
738                 g_signal_handlers_block_by_func (view,
739                                                  contact_list_view_row_expand_or_collapse_cb,
740                                                  GINT_TO_POINTER (TRUE));
741                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
742                 g_signal_handlers_unblock_by_func (view,
743                                                    contact_list_view_row_expand_or_collapse_cb,
744                                                    GINT_TO_POINTER (TRUE));
745         } else {
746                 g_signal_handlers_block_by_func (view,
747                                                  contact_list_view_row_expand_or_collapse_cb,
748                                                  GINT_TO_POINTER (FALSE));
749                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
750                 g_signal_handlers_unblock_by_func (view,
751                                                    contact_list_view_row_expand_or_collapse_cb,
752                                                    GINT_TO_POINTER (FALSE));
753         }
754
755         g_free (name);
756 }
757
758 static void
759 contact_list_view_contact_received (GossipContactListView *view,
760                                     GossipContact         *contact,
761                                     GdkDragAction          action,
762                                     const gchar           *old_group,
763                                     const gchar           *new_group)
764 {
765         GossipContactListViewPriv *priv;
766         GList                     *groups, *l;
767         GList                     *new_groups_list = NULL;
768
769         priv = GET_PRIV (view);
770
771         groups = gossip_contact_get_groups (contact);
772         for (l = groups; l; l = l->next) {
773                 gchar *str;
774
775                 str = l->data;
776
777                 if (action == GDK_ACTION_MOVE &&
778                     old_group != NULL &&
779                     strcmp (str, old_group) == 0) {
780                         continue;
781                 }
782
783                 if (new_group && strcmp (str, new_group) == 0) {
784                         /* Otherwise we set it twice */
785                         continue;
786                 }
787
788                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (str));
789         }
790
791         if (new_group) {
792                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (new_group));
793         }
794
795         gossip_contact_set_groups (contact, new_groups_list);
796 }
797
798 static void
799 contact_list_view_drag_data_received (GtkWidget         *widget,
800                                       GdkDragContext    *context,
801                                       gint               x,
802                                       gint               y,
803                                       GtkSelectionData  *selection,
804                                       guint              info,
805                                       guint              time,
806                                       gpointer           user_data)
807 {
808         GossipContactListViewPriv *priv;
809         EmpathyContactManager     *manager;
810         GtkTreeModel              *model;
811         GtkTreePath               *path;
812         GtkTreeViewDropPosition    position;
813         GossipContact             *contact;
814         const gchar               *id;
815         gchar                     *new_group = NULL;
816         gchar                     *old_group = NULL;
817         gboolean                   is_row;
818
819         priv = GET_PRIV (widget);
820
821         id = (const gchar*) selection->data;
822         gossip_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
823                       context->action == GDK_ACTION_MOVE ? "move" : "",
824                       context->action == GDK_ACTION_COPY ? "copy" : "",
825                       id);
826
827         /* FIXME: This is ambigous, an id can come from multiple accounts */
828         manager = empathy_contact_manager_new ();
829         contact = empathy_contact_list_find (EMPATHY_CONTACT_LIST (manager), id);
830         g_object_unref (manager);
831
832         if (!contact) {
833                 gossip_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
834                 return;
835         }
836
837         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
838
839         /* Get source group information. */
840         if (priv->drag_row) {
841                 path = gtk_tree_row_reference_get_path (priv->drag_row);
842                 if (path) {
843                         old_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
844                         gtk_tree_path_free (path);
845                 }
846         }
847
848         /* Get destination group information. */
849         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
850                                                     x,
851                                                     y,
852                                                     &path,
853                                                     &position);
854
855         if (is_row) {
856                 new_group = gossip_contact_list_store_get_parent_group (model, path, NULL);
857                 gtk_tree_path_free (path);
858         }
859
860         gossip_debug (DEBUG_DOMAIN,
861                       "contact '%s' dragged from '%s' to '%s'",
862                       gossip_contact_get_name (contact),
863                       old_group, new_group);
864
865         if (priv->drag_received) {
866                 priv->drag_received (contact,
867                                      context->action,
868                                      old_group,
869                                      new_group,
870                                      priv->drag_received_data);
871         } else {
872                 contact_list_view_contact_received (GOSSIP_CONTACT_LIST_VIEW (widget),
873                                                     contact,
874                                                     context->action,
875                                                     old_group,
876                                                     new_group);
877         }
878
879         g_free (old_group);
880         g_free (new_group);
881
882         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
883 }
884
885 static gboolean
886 contact_list_view_drag_motion (GtkWidget      *widget,
887                                GdkDragContext *context,
888                                gint            x,
889                                gint            y,
890                                guint           time,
891                                gpointer        data)
892 {
893         static DragMotionData *dm = NULL;
894         GtkTreePath           *path;
895         gboolean               is_row;
896         gboolean               is_different = FALSE;
897         gboolean               cleanup = TRUE;
898
899         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
900                                                 x,
901                                                 y,
902                                                 &path,
903                                                 NULL,
904                                                 NULL,
905                                                 NULL);
906
907         cleanup &= (!dm);
908
909         if (is_row) {
910                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
911                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
912         } else {
913                 cleanup &= FALSE;
914         }
915
916         if (!is_different && !cleanup) {
917                 return TRUE;
918         }
919
920         if (dm) {
921                 gtk_tree_path_free (dm->path);
922                 if (dm->timeout_id) {
923                         g_source_remove (dm->timeout_id);
924                 }
925
926                 g_free (dm);
927
928                 dm = NULL;
929         }
930
931         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
932                 dm = g_new0 (DragMotionData, 1);
933
934                 dm->view = GOSSIP_CONTACT_LIST_VIEW (widget);
935                 dm->path = gtk_tree_path_copy (path);
936
937                 dm->timeout_id = g_timeout_add (
938                         1500,
939                         (GSourceFunc) contact_list_view_drag_motion_cb,
940                         dm);
941         }
942
943         return TRUE;
944 }
945
946 static gboolean
947 contact_list_view_drag_motion_cb (DragMotionData *data)
948 {
949         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
950                                   data->path,
951                                   FALSE);
952
953         data->timeout_id = 0;
954
955         return FALSE;
956 }
957
958 static void
959 contact_list_view_drag_begin (GtkWidget      *widget,
960                               GdkDragContext *context,
961                               gpointer        user_data)
962 {
963         GossipContactListViewPriv *priv;
964         GtkTreeSelection          *selection;
965         GtkTreeModel              *model;
966         GtkTreePath               *path;
967         GtkTreeIter                iter;
968
969         priv = GET_PRIV (widget);
970
971         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
972         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
973                 return;
974         }
975
976         path = gtk_tree_model_get_path (model, &iter);
977         priv->drag_row = gtk_tree_row_reference_new (model, path);
978         gtk_tree_path_free (path);
979 }
980
981 static void
982 contact_list_view_drag_data_get (GtkWidget        *widget,
983                                  GdkDragContext   *context,
984                                  GtkSelectionData *selection,
985                                  guint             info,
986                                  guint             time,
987                                  gpointer          user_data)
988 {
989         GossipContactListViewPriv *priv;
990         GtkTreePath               *src_path;
991         GtkTreeIter                iter;
992         GtkTreeModel              *model;
993         GossipContact             *contact;
994         const gchar               *id;
995
996         priv = GET_PRIV (widget);
997
998         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
999         if (!priv->drag_row) {
1000                 return;
1001         }
1002
1003         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
1004         if (!src_path) {
1005                 return;
1006         }
1007
1008         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
1009                 gtk_tree_path_free (src_path);
1010                 return;
1011         }
1012
1013         gtk_tree_path_free (src_path);
1014
1015         contact = gossip_contact_list_view_get_selected (GOSSIP_CONTACT_LIST_VIEW (widget));
1016         if (!contact) {
1017                 return;
1018         }
1019
1020         id = gossip_contact_get_id (contact);
1021         g_object_unref (contact);
1022
1023         switch (info) {
1024         case DND_DRAG_TYPE_CONTACT_ID:
1025                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1026                                         (guchar*)id, strlen (id) + 1);
1027                 break;
1028
1029         default:
1030                 return;
1031         }
1032 }
1033
1034 static void
1035 contact_list_view_drag_end (GtkWidget      *widget,
1036                             GdkDragContext *context,
1037                             gpointer        user_data)
1038 {
1039         GossipContactListViewPriv *priv;
1040
1041         priv = GET_PRIV (widget);
1042
1043         if (priv->drag_row) {
1044                 gtk_tree_row_reference_free (priv->drag_row);
1045                 priv->drag_row = NULL;
1046         }
1047 }
1048
1049 static void
1050 contact_list_view_cell_set_background (GossipContactListView *view,
1051                                        GtkCellRenderer       *cell,
1052                                        gboolean               is_group,
1053                                        gboolean               is_active)
1054 {
1055         GdkColor  color;
1056         GtkStyle *style;
1057
1058         style = gtk_widget_get_style (GTK_WIDGET (view));
1059
1060         if (!is_group) {
1061                 if (is_active) {
1062                         color = style->bg[GTK_STATE_SELECTED];
1063
1064                         /* Here we take the current theme colour and add it to
1065                          * the colour for white and average the two. This
1066                          * gives a colour which is inline with the theme but
1067                          * slightly whiter.
1068                          */
1069                         color.red = (color.red + (style->white).red) / 2;
1070                         color.green = (color.green + (style->white).green) / 2;
1071                         color.blue = (color.blue + (style->white).blue) / 2;
1072
1073                         g_object_set (cell,
1074                                       "cell-background-gdk", &color,
1075                                       NULL);
1076                 } else {
1077                         g_object_set (cell,
1078                                       "cell-background-gdk", NULL,
1079                                       NULL);
1080                 }
1081         } else {
1082 #if 0
1083                 gint color_sum_normal;
1084                 gint color_sum_selected;
1085                 
1086                 color = style->base[GTK_STATE_SELECTED];
1087                 color_sum_normal = color.red+color.green+color.blue;
1088                 color = style->base[GTK_STATE_NORMAL];
1089                 color_sum_selected = color.red+color.green+color.blue;
1090                 color = style->text_aa[GTK_STATE_INSENSITIVE];
1091
1092                 if (color_sum_normal < color_sum_selected) { 
1093                         /* Found a light theme */
1094                         color.red = (color.red + (style->white).red) / 2;
1095                         color.green = (color.green + (style->white).green) / 2;
1096                         color.blue = (color.blue + (style->white).blue) / 2;
1097                 } else { 
1098                         /* Found a dark theme */
1099                         color.red = (color.red + (style->black).red) / 2;
1100                         color.green = (color.green + (style->black).green) / 2;
1101                         color.blue = (color.blue + (style->black).blue) / 2;
1102                 }
1103
1104                 g_object_set (cell,
1105                               "cell-background-gdk", &color,
1106                               NULL);
1107 #endif
1108         }
1109 }
1110
1111 static void
1112 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
1113                                          GtkCellRenderer       *cell,
1114                                          GtkTreeModel          *model,
1115                                          GtkTreeIter           *iter,
1116                                          GossipContactListView *view)
1117 {
1118         gchar    *icon_name;
1119         gboolean  is_group;
1120         gboolean  is_active;
1121
1122         gtk_tree_model_get (model, iter,
1123                             COL_IS_GROUP, &is_group,
1124                             COL_IS_ACTIVE, &is_active,
1125                             COL_ICON_STATUS, &icon_name,
1126                             -1);
1127
1128         g_object_set (cell,
1129                       "visible", !is_group,
1130                       "icon-name", icon_name,
1131                       NULL);
1132
1133         g_free (icon_name);
1134
1135         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1136 }
1137
1138 static void
1139 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
1140                                          GtkCellRenderer       *cell,
1141                                          GtkTreeModel          *model,
1142                                          GtkTreeIter           *iter,
1143                                          GossipContactListView *view)
1144 {
1145         GdkPixbuf *pixbuf;
1146         gboolean   show_avatar;
1147         gboolean   is_group;
1148         gboolean   is_active;
1149
1150         gtk_tree_model_get (model, iter,
1151                             COL_PIXBUF_AVATAR, &pixbuf,
1152                             COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1153                             COL_IS_GROUP, &is_group,
1154                             COL_IS_ACTIVE, &is_active,
1155                             -1);
1156
1157         g_object_set (cell,
1158                       "visible", !is_group && show_avatar,
1159                       "pixbuf", pixbuf,
1160                       NULL);
1161
1162         if (pixbuf) {
1163                 g_object_unref (pixbuf);
1164         }
1165
1166         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1167 }
1168
1169 static void
1170 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
1171                                        GtkCellRenderer       *cell,
1172                                        GtkTreeModel          *model,
1173                                        GtkTreeIter           *iter,
1174                                        GossipContactListView *view)
1175 {
1176         gboolean is_group;
1177         gboolean is_active;
1178         gboolean show_status;
1179
1180         gtk_tree_model_get (model, iter,
1181                             COL_IS_GROUP, &is_group,
1182                             COL_IS_ACTIVE, &is_active,
1183                             COL_STATUS_VISIBLE, &show_status,
1184                             -1);
1185
1186         g_object_set (cell,
1187                       "show-status", show_status,
1188                       NULL);
1189
1190         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1191 }
1192
1193 static void
1194 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
1195                                            GtkCellRenderer       *cell,
1196                                            GtkTreeModel          *model,
1197                                            GtkTreeIter           *iter,
1198                                            GossipContactListView *view)
1199 {
1200         gboolean is_group;
1201         gboolean is_active;
1202
1203         gtk_tree_model_get (model, iter,
1204                             COL_IS_GROUP, &is_group,
1205                             COL_IS_ACTIVE, &is_active,
1206                             -1);
1207
1208         if (gtk_tree_model_iter_has_child (model, iter)) {
1209                 GtkTreePath *path;
1210                 gboolean     row_expanded;
1211
1212                 path = gtk_tree_model_get_path (model, iter);
1213                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
1214                 gtk_tree_path_free (path);
1215
1216                 g_object_set (cell,
1217                               "visible", TRUE,
1218                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1219                               NULL);
1220         } else {
1221                 g_object_set (cell, "visible", FALSE, NULL);
1222         }
1223
1224         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1225 }
1226
1227 static GtkWidget *
1228 contact_list_view_get_contact_menu (GossipContactListView *view,
1229                                     gboolean               can_send_file,
1230                                     gboolean               can_show_log)
1231 {
1232         GossipContactListViewPriv *priv;
1233         GtkAction                 *action;
1234         GtkWidget                 *widget;
1235
1236         priv = GET_PRIV (view);
1237
1238         /* Sort out sensitive items */
1239         action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
1240         gtk_action_set_sensitive (action, can_show_log);
1241
1242         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
1243         gtk_action_set_visible (action, can_send_file);
1244
1245         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
1246
1247         return widget;
1248 }
1249
1250 static gboolean
1251 contact_list_view_button_press_event_cb (GossipContactListView *view,
1252                                          GdkEventButton        *event,
1253                                          gpointer               user_data)
1254 {
1255         GossipContactListViewPriv *priv;
1256         GossipContact             *contact;
1257         GtkTreePath               *path;
1258         GtkTreeSelection          *selection;
1259         GtkTreeModel              *model;
1260         GtkTreeIter                iter;
1261         gboolean                   row_exists;
1262         GtkWidget                 *menu;
1263
1264         if (event->button != 3) {
1265                 return FALSE;
1266         }
1267
1268         priv = GET_PRIV (view);
1269
1270         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1271         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1272
1273         gtk_widget_grab_focus (GTK_WIDGET (view));
1274
1275         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view),
1276                                                     event->x, event->y,
1277                                                     &path,
1278                                                     NULL, NULL, NULL);
1279         if (!row_exists) {
1280                 return FALSE;
1281         }
1282
1283         gtk_tree_selection_unselect_all (selection);
1284         gtk_tree_selection_select_path (selection, path);
1285
1286         gtk_tree_model_get_iter (model, &iter, path);
1287         gtk_tree_path_free (path);
1288
1289         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1290
1291         if (contact) {
1292                 menu = gossip_contact_list_view_get_contact_menu (view, contact);
1293                 g_object_unref (contact);
1294         } else {
1295                 menu = gossip_contact_list_view_get_group_menu (view);
1296         }
1297
1298         if (!menu) {
1299                 return FALSE;
1300         }
1301
1302         gtk_widget_show (menu);
1303
1304         gtk_menu_popup (GTK_MENU (menu),
1305                         NULL, NULL, NULL, NULL,
1306                         event->button, event->time);
1307
1308         return TRUE;
1309 }
1310
1311 static void
1312 contact_list_view_row_activated_cb (GossipContactListView *view,
1313                                     GtkTreePath           *path,
1314                                     GtkTreeViewColumn     *col,
1315                                     gpointer               user_data)
1316 {
1317         GossipContact *contact;
1318         GtkTreeModel  *model;
1319         GtkTreeIter    iter;
1320
1321         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1322
1323         gtk_tree_model_get_iter (model, &iter, path);
1324         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1325
1326         if (contact) {
1327                 contact_list_view_action_activated (view, contact);
1328                 g_object_unref (contact);
1329         }
1330 }
1331
1332 static void
1333 contact_list_view_row_expand_or_collapse_cb (GossipContactListView *view,
1334                                              GtkTreeIter           *iter,
1335                                              GtkTreePath           *path,
1336                                              gpointer               user_data)
1337 {
1338         GtkTreeModel *model;
1339         gchar        *name;
1340         gboolean      expanded;
1341
1342         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1343
1344         gtk_tree_model_get (model, iter,
1345                             COL_NAME, &name,
1346                             -1);
1347
1348         expanded = GPOINTER_TO_INT (user_data);
1349         gossip_contact_group_set_expanded (name, expanded);
1350
1351         g_free (name);
1352 }
1353
1354 static gboolean 
1355 contact_list_view_filter_show_contact (GossipContact *contact,
1356                                        const gchar   *filter)
1357 {
1358         gchar    *str;
1359         gboolean  visible;
1360
1361         /* Check contact id */
1362         str = g_utf8_casefold (gossip_contact_get_id (contact), -1);
1363         visible = G_STR_EMPTY (str) || strstr (str, filter);
1364         g_free (str);
1365
1366         if (visible) {
1367                 return TRUE;
1368         }
1369
1370         /* Check contact name */
1371         str = g_utf8_casefold (gossip_contact_get_name (contact), -1);
1372         visible = G_STR_EMPTY (str) || strstr (str, filter);
1373         g_free (str);
1374         
1375         return visible;
1376 }
1377
1378 static gboolean
1379 contact_list_view_filter_show_group (GossipContactListView *view,
1380                                      const gchar           *group,
1381                                      const gchar           *filter)
1382 {
1383         GossipContactListViewPriv *priv;
1384         EmpathyContactList        *list;
1385         GList                     *contacts, *l;
1386         gchar                     *str;
1387         gboolean                   show_group = FALSE;
1388
1389         priv = GET_PRIV (view);
1390         
1391         str = g_utf8_casefold (group, -1);
1392         if (!str) {
1393                 return FALSE;
1394         }
1395
1396         /* If the filter is the partially the group name, we show the
1397          * whole group.
1398          */
1399         if (strstr (str, filter)) {
1400                 g_free (str);
1401                 return TRUE;
1402         }
1403
1404         /* At this point, we need to check in advance if this
1405          * group should be shown because a contact we want to
1406          * show exists in it.
1407          */
1408         list = gossip_contact_list_store_get_list_iface (priv->store);
1409         contacts = empathy_contact_list_get_contacts (list);
1410         for (l = contacts; l && !show_group; l = l->next) {
1411                 if (!gossip_contact_is_in_group (l->data, group)) {
1412                         continue;
1413                 }
1414
1415                 if (contact_list_view_filter_show_contact (l->data, filter)) {
1416                         show_group = TRUE;
1417                 }
1418         }
1419         g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
1420         g_list_free (contacts);
1421         g_free (str);
1422
1423         return show_group;
1424 }
1425
1426 static gboolean
1427 contact_list_view_filter_func (GtkTreeModel          *model,
1428                                GtkTreeIter           *iter,
1429                                GossipContactListView *view)
1430 {
1431         GossipContactListViewPriv *priv;
1432         gboolean                   is_group;
1433         gboolean                   is_separator;
1434         gboolean                   visible = TRUE;
1435
1436         priv = GET_PRIV (view);
1437
1438         if (G_STR_EMPTY (priv->filter_text)) {
1439                 return TRUE;
1440         }
1441         
1442         /* Check to see if iter matches any group names */
1443         gtk_tree_model_get (model, iter,
1444                             COL_IS_GROUP, &is_group,
1445                             COL_IS_SEPARATOR, &is_separator,
1446                             -1);
1447
1448         if (is_group) {
1449                 gchar *name;
1450
1451                 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1452                 visible &= contact_list_view_filter_show_group (view,
1453                                                                 name,
1454                                                                 priv->filter_text);
1455                 g_free (name);
1456         } else if (is_separator) {
1457                 /* Do nothing here */
1458         } else {
1459                 GossipContact *contact;
1460
1461                 /* Check contact id */
1462                 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
1463                 visible &= contact_list_view_filter_show_contact (contact, 
1464                                                                   priv->filter_text);
1465                 g_object_unref (contact);
1466         }
1467
1468         return visible;
1469 }
1470
1471 static void
1472 contact_list_view_action_cb (GtkAction             *action,
1473                              GossipContactListView *view)
1474 {
1475         GossipContact *contact;
1476         const gchar   *name;
1477         gchar         *group;
1478
1479         name = gtk_action_get_name (action);
1480         if (!name) {
1481                 return;
1482         }
1483
1484         gossip_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
1485
1486         contact = gossip_contact_list_view_get_selected (view);
1487         group = gossip_contact_list_view_get_selected_group (view);
1488
1489         if (contact && strcmp (name, "Chat") == 0) {
1490                 contact_list_view_action_activated (view, contact);
1491         }
1492         else if (contact && strcmp (name, "Information") == 0) {
1493         }
1494         else if (contact && strcmp (name, "Edit") == 0) {
1495         }
1496         else if (contact && strcmp (name, "Remove") == 0) {
1497         }
1498         else if (contact && strcmp (name, "Invite") == 0) {
1499         }
1500         else if (contact && strcmp (name, "SendFile") == 0) {
1501         }
1502         else if (contact && strcmp (name, "Log") == 0) {
1503         }
1504         else if (group && strcmp (name, "Rename") == 0) {
1505         }
1506
1507         g_free (group);
1508         if (contact) {
1509                 g_object_unref (contact);
1510         }
1511 }
1512
1513 static void
1514 contact_list_view_action_activated (GossipContactListView *view,
1515                                     GossipContact         *contact)
1516 {
1517         MissionControl *mc;
1518
1519         mc = gossip_mission_control_new ();
1520         mission_control_request_channel (mc,
1521                                          gossip_contact_get_account (contact),
1522                                          TP_IFACE_CHANNEL_TYPE_TEXT,
1523                                          gossip_contact_get_handle (contact),
1524                                          TP_HANDLE_TYPE_CONTACT,
1525                                          NULL, NULL);
1526         g_object_unref (mc);
1527 }
1528