]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Remove the autogen.sh script and use gnome-autogen.sh instead.
[empathy.git] / libempathy-gtk / empathy-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/empathy-log-manager.h>
40 #include <libempathy/empathy-debug.h>
41 #include <libempathy/empathy-utils.h>
42 #include <libempathy/empathy-marshal.h>
43
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-contact-groups.h"
48 #include "empathy-cell-renderer-expander.h"
49 #include "empathy-cell-renderer-text.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-contact-dialogs.h"
52 //#include "empathy-chat-invite.h"
53 //#include "empathy-ft-window.h"
54 #include "empathy-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), EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv))
66
67 struct _EmpathyContactListViewPriv {
68         EmpathyContactListStore *store;
69         GtkUIManager           *ui;
70         GtkTreeRowReference    *drag_row;
71         GtkTreeModel           *filter;
72         gchar                  *filter_text;
73
74         EmpathyContactListViewDragReceivedFunc drag_received;
75         gpointer                              drag_received_data;
76 };
77
78 typedef struct {
79         EmpathyContactListView *view;
80         GtkTreePath           *path;
81         guint                  timeout_id;
82 } DragMotionData;
83
84 typedef struct {
85         EmpathyContactListView *view;
86         EmpathyContact         *contact;
87         gboolean               remove;
88 } ShowActiveData;
89
90 static void        empathy_contact_list_view_class_init         (EmpathyContactListViewClass *klass);
91 static void        empathy_contact_list_view_init               (EmpathyContactListView      *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                     (EmpathyContactListView      *view);
102 static void        contact_list_view_row_has_child_toggled_cb  (GtkTreeModel               *model,
103                                                                 GtkTreePath                *path,
104                                                                 GtkTreeIter                *iter,
105                                                                 EmpathyContactListView      *view);
106 static void        contact_list_view_contact_received          (EmpathyContactListView      *view,
107                                                                 EmpathyContact              *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 static gboolean    contact_list_view_drag_motion               (GtkWidget                  *widget,
119                                                                 GdkDragContext             *context,
120                                                                 gint                        x,
121                                                                 gint                        y,
122                                                                 guint                       time);
123 static gboolean    contact_list_view_drag_motion_cb            (DragMotionData             *data);
124 static void        contact_list_view_drag_begin                (GtkWidget                  *widget,
125                                                                 GdkDragContext             *context);
126 static void        contact_list_view_drag_data_get             (GtkWidget                  *widget,
127                                                                 GdkDragContext             *context,
128                                                                 GtkSelectionData           *selection,
129                                                                 guint                       info,
130                                                                 guint                       time);
131 static void        contact_list_view_drag_end                  (GtkWidget                  *widget,
132                                                                 GdkDragContext             *context);
133 static gboolean    contact_list_view_drag_drop                 (GtkWidget                  *widget,
134                                                                 GdkDragContext             *drag_context,
135                                                                 gint                        x,
136                                                                 gint                        y,
137                                                                 guint                       time);
138 static void        contact_list_view_cell_set_background       (EmpathyContactListView      *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                                                                 EmpathyContactListView      *view);
147 static void        contact_list_view_avatar_cell_data_func     (GtkTreeViewColumn          *tree_column,
148                                                                 GtkCellRenderer            *cell,
149                                                                 GtkTreeModel               *model,
150                                                                 GtkTreeIter                *iter,
151                                                                 EmpathyContactListView      *view);
152 static void        contact_list_view_text_cell_data_func       (GtkTreeViewColumn          *tree_column,
153                                                                 GtkCellRenderer            *cell,
154                                                                 GtkTreeModel               *model,
155                                                                 GtkTreeIter                *iter,
156                                                                 EmpathyContactListView      *view);
157 static void        contact_list_view_expander_cell_data_func   (GtkTreeViewColumn          *column,
158                                                                 GtkCellRenderer            *cell,
159                                                                 GtkTreeModel               *model,
160                                                                 GtkTreeIter                *iter,
161                                                                 EmpathyContactListView      *view);
162 static GtkWidget * contact_list_view_get_contact_menu          (EmpathyContactListView      *view,
163                                                                 gboolean                    can_send_file,
164                                                                 gboolean                    can_show_log);
165 static gboolean    contact_list_view_button_press_event_cb     (EmpathyContactListView      *view,
166                                                                 GdkEventButton             *event,
167                                                                 gpointer                    user_data);
168 static void        contact_list_view_row_activated_cb          (EmpathyContactListView      *view,
169                                                                 GtkTreePath                *path,
170                                                                 GtkTreeViewColumn          *col,
171                                                                 gpointer                    user_data);
172 static void        contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView      *view,
173                                                                 GtkTreeIter                *iter,
174                                                                 GtkTreePath                *path,
175                                                                 gpointer                    user_data);
176 static gboolean    contact_list_view_filter_show_contact       (EmpathyContact              *contact,
177                                                                 const gchar                *filter);
178 static gboolean    contact_list_view_filter_show_group         (EmpathyContactListView      *view,
179                                                                 const gchar                *group,
180                                                                 const gchar                *filter);
181 static gboolean    contact_list_view_filter_func               (GtkTreeModel               *model,
182                                                                 GtkTreeIter                *iter,
183                                                                 EmpathyContactListView      *view);
184 static void        contact_list_view_action_cb                 (GtkAction                  *action,
185                                                                 EmpathyContactListView      *view);
186 static void        contact_list_view_action_activated          (EmpathyContactListView      *view,
187                                                                 EmpathyContact              *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 (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
286
287 static void
288 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
289 {
290         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
291         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
292
293         object_class->finalize = contact_list_view_finalize;
294         object_class->get_property = contact_list_view_get_property;
295         object_class->set_property = contact_list_view_set_property;
296
297         widget_class->drag_data_received = contact_list_view_drag_data_received;
298         widget_class->drag_drop          = contact_list_view_drag_drop;
299         widget_class->drag_begin         = contact_list_view_drag_begin;
300         widget_class->drag_data_get      = contact_list_view_drag_data_get;
301         widget_class->drag_end           = contact_list_view_drag_end;
302         /* FIXME: noticed but when you drag the row over the treeview
303          * fast, it seems to stop redrawing itself, if we don't
304          * connect this signal, all is fine.
305          */
306         widget_class->drag_motion        = contact_list_view_drag_motion;
307
308         signals[DRAG_CONTACT_RECEIVED] =
309                 g_signal_new ("drag-contact-received",
310                               G_OBJECT_CLASS_TYPE (klass),
311                               G_SIGNAL_RUN_LAST,
312                               0,
313                               NULL, NULL,
314                               empathy_marshal_VOID__OBJECT_STRING_STRING,
315                               G_TYPE_NONE,
316                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
317
318         g_object_class_install_property (object_class,
319                                          PROP_FILTER,
320                                          g_param_spec_string ("filter",
321                                                               "Filter",
322                                                               "The text to use to filter the contact list",
323                                                               NULL,
324                                                               G_PARAM_READWRITE));
325
326         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
327 }
328
329 static void
330 empathy_contact_list_view_init (EmpathyContactListView *view)
331 {
332         EmpathyContactListViewPriv *priv;
333         GtkActionGroup            *action_group;
334         GError                    *error = NULL;
335
336         priv = GET_PRIV (view);
337
338         /* Get saved group states. */
339         empathy_contact_groups_get_all ();
340
341         /* Set up UI Manager */
342         priv->ui = gtk_ui_manager_new ();
343
344         action_group = gtk_action_group_new ("Actions");
345         gtk_action_group_set_translation_domain (action_group, GETTEXT_PACKAGE);
346         gtk_action_group_add_actions (action_group, entries, n_entries, view);
347         gtk_ui_manager_insert_action_group (priv->ui, action_group, 0);
348
349         if (!gtk_ui_manager_add_ui_from_string (priv->ui, ui_info, -1, &error)) {
350                 g_warning ("Could not build contact menus from string:'%s'", error->message);
351                 g_error_free (error);
352         }
353
354         g_object_unref (action_group);
355
356         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
357                                               empathy_contact_list_store_row_separator_func,
358                                               NULL, NULL);
359
360         /* Connect to tree view signals rather than override. */
361         g_signal_connect (view,
362                           "button-press-event",
363                           G_CALLBACK (contact_list_view_button_press_event_cb),
364                           NULL);
365         g_signal_connect (view,
366                           "row-activated",
367                           G_CALLBACK (contact_list_view_row_activated_cb),
368                           NULL);
369         g_signal_connect (view,
370                           "row-expanded",
371                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
372                           GINT_TO_POINTER (TRUE));
373         g_signal_connect (view,
374                           "row-collapsed",
375                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
376                           GINT_TO_POINTER (FALSE));
377 }
378
379 static void
380 contact_list_view_finalize (GObject *object)
381 {
382         EmpathyContactListViewPriv *priv;
383
384         priv = GET_PRIV (object);
385
386         if (priv->ui) {
387                 g_object_unref (priv->ui);
388         }
389         if (priv->store) {
390                 g_object_unref (priv->store);
391         }
392         if (priv->filter) {
393                 g_object_unref (priv->filter);
394         }
395         g_free (priv->filter_text);
396
397         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
398 }
399
400 static void
401 contact_list_view_get_property (GObject    *object,
402                                 guint       param_id,
403                                 GValue     *value,
404                                 GParamSpec *pspec)
405 {
406         EmpathyContactListViewPriv *priv;
407
408         priv = GET_PRIV (object);
409
410         switch (param_id) {
411         case PROP_FILTER:
412                 g_value_set_string (value, priv->filter_text);
413                 break;
414         default:
415                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
416                 break;
417         };
418 }
419
420 static void
421 contact_list_view_set_property (GObject      *object,
422                                 guint         param_id,
423                                 const GValue *value,
424                                 GParamSpec   *pspec)
425 {
426         EmpathyContactListViewPriv *priv;
427
428         priv = GET_PRIV (object);
429
430         switch (param_id) {
431         case PROP_FILTER:
432                 empathy_contact_list_view_set_filter (EMPATHY_CONTACT_LIST_VIEW (object),
433                                                 g_value_get_string (value));
434                 break;
435         default:
436                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
437                 break;
438         };
439 }
440
441 EmpathyContactListView *
442 empathy_contact_list_view_new (EmpathyContactListStore *store)
443 {
444         EmpathyContactListViewPriv *priv;
445         EmpathyContactListView     *view;
446         
447         view = g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW, NULL);
448         priv = GET_PRIV (view);
449
450         priv->store = g_object_ref (store);
451         contact_list_view_setup (view);
452
453         return view;
454 }
455
456 EmpathyContact *
457 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
458 {
459         EmpathyContactListViewPriv *priv;
460         GtkTreeSelection          *selection;
461         GtkTreeIter                iter;
462         GtkTreeModel              *model;
463         EmpathyContact             *contact;
464
465         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
466
467         priv = GET_PRIV (view);
468
469         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
470         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
471                 return NULL;
472         }
473
474         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
475
476         return contact;
477 }
478
479 gchar *
480 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
481 {
482         EmpathyContactListViewPriv *priv;
483         GtkTreeSelection          *selection;
484         GtkTreeIter                iter;
485         GtkTreeModel              *model;
486         gboolean                   is_group;
487         gchar                     *name;
488
489         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
490
491         priv = GET_PRIV (view);
492
493         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
494         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
495                 return NULL;
496         }
497
498         gtk_tree_model_get (model, &iter,
499                             COL_IS_GROUP, &is_group,
500                             COL_NAME, &name,
501                             -1);
502
503         if (!is_group) {
504                 g_free (name);
505                 return NULL;
506         }
507
508         return name;
509 }
510
511 GtkWidget *
512 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
513 {
514         EmpathyContactListViewPriv *priv;
515         GtkWidget                 *widget;
516
517         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
518
519         priv = GET_PRIV (view);
520
521         widget = gtk_ui_manager_get_widget (priv->ui, "/Group");
522
523         return widget;
524 }
525
526 GtkWidget *
527 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view,
528                                            EmpathyContact         *contact)
529 {
530         EmpathyLogManager *log_manager;
531         gboolean           can_show_log;
532         gboolean           can_send_file;
533
534         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
535         g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
536
537         log_manager = empathy_log_manager_new ();
538         can_show_log = empathy_log_manager_exists (log_manager,
539                                                    empathy_contact_get_account (contact),
540                                                    empathy_contact_get_id (contact),
541                                                    FALSE);
542         can_send_file = FALSE;
543         g_object_unref (log_manager);
544
545         return contact_list_view_get_contact_menu (view,
546                                                    can_send_file,
547                                                    can_show_log);
548 }
549
550 void
551 empathy_contact_list_view_set_filter (EmpathyContactListView *view,
552                                      const gchar           *filter)
553 {
554         EmpathyContactListViewPriv *priv;
555
556         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
557
558         priv = GET_PRIV (view);
559
560         g_free (priv->filter_text);
561         if (filter) {
562                 priv->filter_text = g_utf8_casefold (filter, -1);
563         } else {
564                 priv->filter_text = NULL;
565         }
566
567         empathy_debug (DEBUG_DOMAIN, "Refiltering with filter:'%s' (case folded)", filter);
568         gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter));
569 }
570
571 void
572 empathy_contact_list_view_set_drag_received_func (EmpathyContactListView                 *view,
573                                                  EmpathyContactListViewDragReceivedFunc  func,
574                                                  gpointer                               user_data)
575 {
576         EmpathyContactListViewPriv *priv;
577
578         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
579
580         priv = GET_PRIV (view);
581
582         if (func) {
583                 priv->drag_received = func;
584                 priv->drag_received_data = user_data;
585         } else {
586                 priv->drag_received = NULL;
587                 priv->drag_received_data = NULL;
588         }
589 }
590
591 static void
592 contact_list_view_setup (EmpathyContactListView *view)
593 {
594         EmpathyContactListViewPriv *priv;
595         GtkCellRenderer           *cell;
596         GtkTreeViewColumn         *col;
597         gint                       i;
598
599         priv = GET_PRIV (view);
600
601         /* Create filter */
602         priv->filter = gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->store), NULL);
603
604         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (priv->filter),
605                                                 (GtkTreeModelFilterVisibleFunc)
606                                                 contact_list_view_filter_func,
607                                                 view, NULL);
608         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
609                                              empathy_contact_list_store_search_equal_func,
610                                              view, NULL);
611         g_signal_connect (priv->filter, "row-has-child-toggled",
612                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
613                           view);
614         gtk_tree_view_set_model (GTK_TREE_VIEW (view), priv->filter);
615
616
617         /* Setup view */
618         g_object_set (view,
619                       "headers-visible", FALSE,
620                       "reorderable", TRUE,
621                       "show-expanders", FALSE,
622                       NULL);
623
624         col = gtk_tree_view_column_new ();
625
626         /* State */
627         cell = gtk_cell_renderer_pixbuf_new ();
628         gtk_tree_view_column_pack_start (col, cell, FALSE);
629         gtk_tree_view_column_set_cell_data_func (
630                 col, cell,
631                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
632                 view, NULL);
633
634         g_object_set (cell,
635                       "xpad", 5,
636                       "ypad", 1,
637                       "visible", FALSE,
638                       NULL);
639
640         /* Name */
641         cell = empathy_cell_renderer_text_new ();
642         gtk_tree_view_column_pack_start (col, cell, TRUE);
643         gtk_tree_view_column_set_cell_data_func (
644                 col, cell,
645                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
646                 view, NULL);
647
648         gtk_tree_view_column_add_attribute (col, cell,
649                                             "name", COL_NAME);
650         gtk_tree_view_column_add_attribute (col, cell,
651                                             "status", COL_STATUS);
652         gtk_tree_view_column_add_attribute (col, cell,
653                                             "is_group", COL_IS_GROUP);
654
655         /* Avatar */
656         cell = gtk_cell_renderer_pixbuf_new ();
657         gtk_tree_view_column_pack_start (col, cell, FALSE);
658         gtk_tree_view_column_set_cell_data_func (
659                 col, cell,
660                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
661                 view, NULL);
662
663         g_object_set (cell,
664                       "xpad", 0,
665                       "ypad", 0,
666                       "visible", FALSE,
667                       "width", 32,
668                       "height", 32,
669                       NULL);
670
671         /* Expander */
672         cell = empathy_cell_renderer_expander_new ();
673         gtk_tree_view_column_pack_end (col, cell, FALSE);
674         gtk_tree_view_column_set_cell_data_func (
675                 col, cell,
676                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
677                 view, NULL);
678
679         /* Actually add the column now we have added all cell renderers */
680         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
681
682         /* Drag & Drop. */
683         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
684                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
685                                                       FALSE);
686         }
687
688         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
689                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
690                                                         FALSE);
691         }
692
693         /* Note: We support the COPY action too, but need to make the
694          * MOVE action the default.
695          */
696         gtk_drag_source_set (GTK_WIDGET (view),
697                              GDK_BUTTON1_MASK,
698                              drag_types_source,
699                              G_N_ELEMENTS (drag_types_source),
700                              GDK_ACTION_MOVE);
701
702         gtk_drag_dest_set (GTK_WIDGET (view),
703                            GTK_DEST_DEFAULT_ALL,
704                            drag_types_dest,
705                            G_N_ELEMENTS (drag_types_dest),
706                            GDK_ACTION_MOVE | GDK_ACTION_LINK);
707 }
708
709 static void
710 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
711                                             GtkTreePath           *path,
712                                             GtkTreeIter           *iter,
713                                             EmpathyContactListView *view)
714 {
715         gboolean  is_group = FALSE;
716         gchar    *name = NULL;
717
718         gtk_tree_model_get (model, iter,
719                             COL_IS_GROUP, &is_group,
720                             COL_NAME, &name,
721                             -1);
722
723         if (!is_group || G_STR_EMPTY (name)) {
724                 g_free (name);
725                 return;
726         }
727
728         if (empathy_contact_group_get_expanded (name)) {
729                 g_signal_handlers_block_by_func (view,
730                                                  contact_list_view_row_expand_or_collapse_cb,
731                                                  GINT_TO_POINTER (TRUE));
732                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
733                 g_signal_handlers_unblock_by_func (view,
734                                                    contact_list_view_row_expand_or_collapse_cb,
735                                                    GINT_TO_POINTER (TRUE));
736         } else {
737                 g_signal_handlers_block_by_func (view,
738                                                  contact_list_view_row_expand_or_collapse_cb,
739                                                  GINT_TO_POINTER (FALSE));
740                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
741                 g_signal_handlers_unblock_by_func (view,
742                                                    contact_list_view_row_expand_or_collapse_cb,
743                                                    GINT_TO_POINTER (FALSE));
744         }
745
746         g_free (name);
747 }
748
749 static void
750 contact_list_view_contact_received (EmpathyContactListView *view,
751                                     EmpathyContact         *contact,
752                                     GdkDragAction          action,
753                                     const gchar           *old_group,
754                                     const gchar           *new_group)
755 {
756         EmpathyContactListViewPriv *priv;
757         GList                     *groups, *l;
758         GList                     *new_groups_list = NULL;
759
760         priv = GET_PRIV (view);
761
762         groups = empathy_contact_get_groups (contact);
763         for (l = groups; l; l = l->next) {
764                 gchar *str;
765
766                 str = l->data;
767
768                 if (action == GDK_ACTION_MOVE &&
769                     old_group != NULL &&
770                     strcmp (str, old_group) == 0) {
771                         continue;
772                 }
773
774                 if (new_group && strcmp (str, new_group) == 0) {
775                         /* Otherwise we set it twice */
776                         continue;
777                 }
778
779                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (str));
780         }
781
782         if (new_group) {
783                 new_groups_list = g_list_prepend (new_groups_list, g_strdup (new_group));
784         }
785
786         empathy_contact_set_groups (contact, new_groups_list);
787 }
788
789 static void
790 contact_list_view_drag_data_received (GtkWidget         *widget,
791                                       GdkDragContext    *context,
792                                       gint               x,
793                                       gint               y,
794                                       GtkSelectionData  *selection,
795                                       guint              info,
796                                       guint              time)
797 {
798         EmpathyContactListViewPriv *priv;
799         EmpathyContactList        *list;
800         GtkTreeModel              *model;
801         GtkTreePath               *path;
802         GtkTreeViewDropPosition    position;
803         EmpathyContact             *contact;
804         const gchar               *id;
805         gchar                     *new_group = NULL;
806         gchar                     *old_group = NULL;
807         gboolean                   is_row;
808
809         priv = GET_PRIV (widget);
810
811         id = (const gchar*) selection->data;
812         empathy_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
813                       context->action == GDK_ACTION_MOVE ? "move" : "",
814                       context->action == GDK_ACTION_COPY ? "copy" : "",
815                       id);
816
817         /* FIXME: This is ambigous, an id can come from multiple accounts */
818         list = empathy_contact_list_store_get_list_iface (priv->store);
819         contact = empathy_contact_list_find (list, id);
820
821         if (!contact) {
822                 empathy_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
823                 return;
824         }
825
826         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
827
828         /* Get source group information. */
829         if (priv->drag_row) {
830                 path = gtk_tree_row_reference_get_path (priv->drag_row);
831                 if (path) {
832                         old_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
833                         gtk_tree_path_free (path);
834                 }
835         }
836
837         /* Get destination group information. */
838         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
839                                                     x,
840                                                     y,
841                                                     &path,
842                                                     &position);
843
844         if (is_row) {
845                 new_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
846                 gtk_tree_path_free (path);
847         }
848
849         empathy_debug (DEBUG_DOMAIN,
850                       "contact '%s' dragged from '%s' to '%s'",
851                       empathy_contact_get_name (contact),
852                       old_group, new_group);
853
854         if (priv->drag_received) {
855                 priv->drag_received (contact,
856                                      context->action,
857                                      old_group,
858                                      new_group,
859                                      priv->drag_received_data);
860         } else {
861                 contact_list_view_contact_received (EMPATHY_CONTACT_LIST_VIEW (widget),
862                                                     contact,
863                                                     context->action,
864                                                     old_group,
865                                                     new_group);
866         }
867
868         g_free (old_group);
869         g_free (new_group);
870
871         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
872 }
873
874 static gboolean
875 contact_list_view_drag_motion (GtkWidget      *widget,
876                                GdkDragContext *context,
877                                gint            x,
878                                gint            y,
879                                guint           time)
880 {
881         static DragMotionData *dm = NULL;
882         GtkTreePath           *path;
883         gboolean               is_row;
884         gboolean               is_different = FALSE;
885         gboolean               cleanup = TRUE;
886
887         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
888                                                 x,
889                                                 y,
890                                                 &path,
891                                                 NULL,
892                                                 NULL,
893                                                 NULL);
894
895         cleanup &= (!dm);
896
897         if (is_row) {
898                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
899                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
900         } else {
901                 cleanup &= FALSE;
902         }
903
904         if (!is_different && !cleanup) {
905                 return TRUE;
906         }
907
908         if (dm) {
909                 gtk_tree_path_free (dm->path);
910                 if (dm->timeout_id) {
911                         g_source_remove (dm->timeout_id);
912                 }
913
914                 g_free (dm);
915
916                 dm = NULL;
917         }
918
919         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
920                 dm = g_new0 (DragMotionData, 1);
921
922                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
923                 dm->path = gtk_tree_path_copy (path);
924
925                 dm->timeout_id = g_timeout_add (
926                         1500,
927                         (GSourceFunc) contact_list_view_drag_motion_cb,
928                         dm);
929         }
930
931         return TRUE;
932 }
933
934 static gboolean
935 contact_list_view_drag_motion_cb (DragMotionData *data)
936 {
937         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
938                                   data->path,
939                                   FALSE);
940
941         data->timeout_id = 0;
942
943         return FALSE;
944 }
945
946 static void
947 contact_list_view_drag_begin (GtkWidget      *widget,
948                               GdkDragContext *context)
949 {
950         EmpathyContactListViewPriv *priv;
951         GtkTreeSelection          *selection;
952         GtkTreeModel              *model;
953         GtkTreePath               *path;
954         GtkTreeIter                iter;
955
956         priv = GET_PRIV (widget);
957
958         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
959                                                                               context);
960
961         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
962         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
963                 return;
964         }
965
966         path = gtk_tree_model_get_path (model, &iter);
967         priv->drag_row = gtk_tree_row_reference_new (model, path);
968         gtk_tree_path_free (path);
969 }
970
971 static void
972 contact_list_view_drag_data_get (GtkWidget        *widget,
973                                  GdkDragContext   *context,
974                                  GtkSelectionData *selection,
975                                  guint             info,
976                                  guint             time)
977 {
978         EmpathyContactListViewPriv *priv;
979         GtkTreePath               *src_path;
980         GtkTreeIter                iter;
981         GtkTreeModel              *model;
982         EmpathyContact             *contact;
983         const gchar               *id;
984
985         priv = GET_PRIV (widget);
986
987         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
988         if (!priv->drag_row) {
989                 return;
990         }
991
992         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
993         if (!src_path) {
994                 return;
995         }
996
997         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
998                 gtk_tree_path_free (src_path);
999                 return;
1000         }
1001
1002         gtk_tree_path_free (src_path);
1003
1004         contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
1005         if (!contact) {
1006                 return;
1007         }
1008
1009         id = empathy_contact_get_id (contact);
1010         g_object_unref (contact);
1011
1012         switch (info) {
1013         case DND_DRAG_TYPE_CONTACT_ID:
1014                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
1015                                         (guchar*)id, strlen (id) + 1);
1016                 break;
1017
1018         default:
1019                 return;
1020         }
1021 }
1022
1023 static void
1024 contact_list_view_drag_end (GtkWidget      *widget,
1025                             GdkDragContext *context)
1026 {
1027         EmpathyContactListViewPriv *priv;
1028
1029         priv = GET_PRIV (widget);
1030
1031         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
1032                                                                             context);
1033
1034         if (priv->drag_row) {
1035                 gtk_tree_row_reference_free (priv->drag_row);
1036                 priv->drag_row = NULL;
1037         }
1038 }
1039
1040 static gboolean
1041 contact_list_view_drag_drop (GtkWidget      *widget,
1042                              GdkDragContext *drag_context,
1043                              gint            x,
1044                              gint            y,
1045                              guint           time)
1046 {
1047         return FALSE;
1048 }
1049
1050 static void
1051 contact_list_view_cell_set_background (EmpathyContactListView *view,
1052                                        GtkCellRenderer       *cell,
1053                                        gboolean               is_group,
1054                                        gboolean               is_active)
1055 {
1056         GdkColor  color;
1057         GtkStyle *style;
1058
1059         style = gtk_widget_get_style (GTK_WIDGET (view));
1060
1061         if (!is_group) {
1062                 if (is_active) {
1063                         color = style->bg[GTK_STATE_SELECTED];
1064
1065                         /* Here we take the current theme colour and add it to
1066                          * the colour for white and average the two. This
1067                          * gives a colour which is inline with the theme but
1068                          * slightly whiter.
1069                          */
1070                         color.red = (color.red + (style->white).red) / 2;
1071                         color.green = (color.green + (style->white).green) / 2;
1072                         color.blue = (color.blue + (style->white).blue) / 2;
1073
1074                         g_object_set (cell,
1075                                       "cell-background-gdk", &color,
1076                                       NULL);
1077                 } else {
1078                         g_object_set (cell,
1079                                       "cell-background-gdk", NULL,
1080                                       NULL);
1081                 }
1082         } else {
1083                 g_object_set (cell,
1084                               "cell-background-gdk", NULL,
1085                               NULL);
1086         }
1087 }
1088
1089 static void
1090 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
1091                                          GtkCellRenderer       *cell,
1092                                          GtkTreeModel          *model,
1093                                          GtkTreeIter           *iter,
1094                                          EmpathyContactListView *view)
1095 {
1096         gchar    *icon_name;
1097         gboolean  is_group;
1098         gboolean  is_active;
1099
1100         gtk_tree_model_get (model, iter,
1101                             COL_IS_GROUP, &is_group,
1102                             COL_IS_ACTIVE, &is_active,
1103                             COL_ICON_STATUS, &icon_name,
1104                             -1);
1105
1106         g_object_set (cell,
1107                       "visible", !is_group,
1108                       "icon-name", icon_name,
1109                       NULL);
1110
1111         g_free (icon_name);
1112
1113         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1114 }
1115
1116 static void
1117 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
1118                                          GtkCellRenderer       *cell,
1119                                          GtkTreeModel          *model,
1120                                          GtkTreeIter           *iter,
1121                                          EmpathyContactListView *view)
1122 {
1123         GdkPixbuf *pixbuf;
1124         gboolean   show_avatar;
1125         gboolean   is_group;
1126         gboolean   is_active;
1127
1128         gtk_tree_model_get (model, iter,
1129                             COL_PIXBUF_AVATAR, &pixbuf,
1130                             COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1131                             COL_IS_GROUP, &is_group,
1132                             COL_IS_ACTIVE, &is_active,
1133                             -1);
1134
1135         g_object_set (cell,
1136                       "visible", !is_group && show_avatar,
1137                       "pixbuf", pixbuf,
1138                       NULL);
1139
1140         if (pixbuf) {
1141                 g_object_unref (pixbuf);
1142         }
1143
1144         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1145 }
1146
1147 static void
1148 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
1149                                        GtkCellRenderer       *cell,
1150                                        GtkTreeModel          *model,
1151                                        GtkTreeIter           *iter,
1152                                        EmpathyContactListView *view)
1153 {
1154         gboolean is_group;
1155         gboolean is_active;
1156         gboolean show_status;
1157
1158         gtk_tree_model_get (model, iter,
1159                             COL_IS_GROUP, &is_group,
1160                             COL_IS_ACTIVE, &is_active,
1161                             COL_STATUS_VISIBLE, &show_status,
1162                             -1);
1163
1164         g_object_set (cell,
1165                       "show-status", show_status,
1166                       NULL);
1167
1168         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1169 }
1170
1171 static void
1172 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
1173                                            GtkCellRenderer       *cell,
1174                                            GtkTreeModel          *model,
1175                                            GtkTreeIter           *iter,
1176                                            EmpathyContactListView *view)
1177 {
1178         gboolean is_group;
1179         gboolean is_active;
1180
1181         gtk_tree_model_get (model, iter,
1182                             COL_IS_GROUP, &is_group,
1183                             COL_IS_ACTIVE, &is_active,
1184                             -1);
1185
1186         if (gtk_tree_model_iter_has_child (model, iter)) {
1187                 GtkTreePath *path;
1188                 gboolean     row_expanded;
1189
1190                 path = gtk_tree_model_get_path (model, iter);
1191                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
1192                 gtk_tree_path_free (path);
1193
1194                 g_object_set (cell,
1195                               "visible", TRUE,
1196                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1197                               NULL);
1198         } else {
1199                 g_object_set (cell, "visible", FALSE, NULL);
1200         }
1201
1202         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1203 }
1204
1205 static GtkWidget *
1206 contact_list_view_get_contact_menu (EmpathyContactListView *view,
1207                                     gboolean               can_send_file,
1208                                     gboolean               can_show_log)
1209 {
1210         EmpathyContactListViewPriv *priv;
1211         GtkAction                 *action;
1212         GtkWidget                 *widget;
1213
1214         priv = GET_PRIV (view);
1215
1216         /* Sort out sensitive items */
1217         action = gtk_ui_manager_get_action (priv->ui, "/Contact/Log");
1218         gtk_action_set_sensitive (action, can_show_log);
1219
1220         action = gtk_ui_manager_get_action (priv->ui, "/Contact/SendFile");
1221         gtk_action_set_visible (action, can_send_file);
1222
1223         widget = gtk_ui_manager_get_widget (priv->ui, "/Contact");
1224
1225         return widget;
1226 }
1227
1228 static gboolean
1229 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
1230                                          GdkEventButton        *event,
1231                                          gpointer               user_data)
1232 {
1233         EmpathyContactListViewPriv *priv;
1234         EmpathyContact             *contact;
1235         GtkTreePath               *path;
1236         GtkTreeSelection          *selection;
1237         GtkTreeModel              *model;
1238         GtkTreeIter                iter;
1239         gboolean                   row_exists;
1240         GtkWidget                 *menu;
1241
1242         if (event->button != 3) {
1243                 return FALSE;
1244         }
1245
1246         priv = GET_PRIV (view);
1247
1248         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1249         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1250
1251         gtk_widget_grab_focus (GTK_WIDGET (view));
1252
1253         row_exists = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view),
1254                                                     event->x, event->y,
1255                                                     &path,
1256                                                     NULL, NULL, NULL);
1257         if (!row_exists) {
1258                 return FALSE;
1259         }
1260
1261         gtk_tree_selection_unselect_all (selection);
1262         gtk_tree_selection_select_path (selection, path);
1263
1264         gtk_tree_model_get_iter (model, &iter, path);
1265         gtk_tree_path_free (path);
1266
1267         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1268
1269         if (contact) {
1270                 menu = empathy_contact_list_view_get_contact_menu (view, contact);
1271                 g_object_unref (contact);
1272         } else {
1273                 menu = empathy_contact_list_view_get_group_menu (view);
1274         }
1275
1276         if (!menu) {
1277                 return FALSE;
1278         }
1279
1280         gtk_widget_show (menu);
1281
1282         gtk_menu_popup (GTK_MENU (menu),
1283                         NULL, NULL, NULL, NULL,
1284                         event->button, event->time);
1285
1286         return TRUE;
1287 }
1288
1289 static void
1290 contact_list_view_row_activated_cb (EmpathyContactListView *view,
1291                                     GtkTreePath           *path,
1292                                     GtkTreeViewColumn     *col,
1293                                     gpointer               user_data)
1294 {
1295         EmpathyContact *contact;
1296         GtkTreeModel  *model;
1297         GtkTreeIter    iter;
1298
1299         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1300
1301         gtk_tree_model_get_iter (model, &iter, path);
1302         gtk_tree_model_get (model, &iter, COL_CONTACT, &contact, -1);
1303
1304         if (contact) {
1305                 contact_list_view_action_activated (view, contact);
1306                 g_object_unref (contact);
1307         }
1308 }
1309
1310 static void
1311 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1312                                              GtkTreeIter           *iter,
1313                                              GtkTreePath           *path,
1314                                              gpointer               user_data)
1315 {
1316         GtkTreeModel *model;
1317         gchar        *name;
1318         gboolean      expanded;
1319
1320         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1321
1322         gtk_tree_model_get (model, iter,
1323                             COL_NAME, &name,
1324                             -1);
1325
1326         expanded = GPOINTER_TO_INT (user_data);
1327         empathy_contact_group_set_expanded (name, expanded);
1328
1329         g_free (name);
1330 }
1331
1332 static gboolean 
1333 contact_list_view_filter_show_contact (EmpathyContact *contact,
1334                                        const gchar   *filter)
1335 {
1336         gchar    *str;
1337         gboolean  visible;
1338
1339         /* Check contact id */
1340         str = g_utf8_casefold (empathy_contact_get_id (contact), -1);
1341         visible = G_STR_EMPTY (str) || strstr (str, filter);
1342         g_free (str);
1343
1344         if (visible) {
1345                 return TRUE;
1346         }
1347
1348         /* Check contact name */
1349         str = g_utf8_casefold (empathy_contact_get_name (contact), -1);
1350         visible = G_STR_EMPTY (str) || strstr (str, filter);
1351         g_free (str);
1352         
1353         return visible;
1354 }
1355
1356 static gboolean
1357 contact_list_view_filter_show_group (EmpathyContactListView *view,
1358                                      const gchar           *group,
1359                                      const gchar           *filter)
1360 {
1361         EmpathyContactListViewPriv *priv;
1362         EmpathyContactList        *list;
1363         GList                     *contacts, *l;
1364         gchar                     *str;
1365         gboolean                   show_group = FALSE;
1366
1367         priv = GET_PRIV (view);
1368         
1369         str = g_utf8_casefold (group, -1);
1370         if (!str) {
1371                 return FALSE;
1372         }
1373
1374         /* If the filter is the partially the group name, we show the
1375          * whole group.
1376          */
1377         if (strstr (str, filter)) {
1378                 g_free (str);
1379                 return TRUE;
1380         }
1381
1382         /* At this point, we need to check in advance if this
1383          * group should be shown because a contact we want to
1384          * show exists in it.
1385          */
1386         list = empathy_contact_list_store_get_list_iface (priv->store);
1387         contacts = empathy_contact_list_get_members (list);
1388         for (l = contacts; l && !show_group; l = l->next) {
1389                 if (!empathy_contact_is_in_group (l->data, group)) {
1390                         g_object_unref (l->data);
1391                         continue;
1392                 }
1393
1394                 if (contact_list_view_filter_show_contact (l->data, filter)) {
1395                         show_group = TRUE;
1396                 }
1397                 g_object_unref (l->data);
1398         }
1399         g_list_free (contacts);
1400         g_free (str);
1401
1402         return show_group;
1403 }
1404
1405 static gboolean
1406 contact_list_view_filter_func (GtkTreeModel          *model,
1407                                GtkTreeIter           *iter,
1408                                EmpathyContactListView *view)
1409 {
1410         EmpathyContactListViewPriv *priv;
1411         gboolean                   is_group;
1412         gboolean                   is_separator;
1413         gboolean                   visible = TRUE;
1414
1415         priv = GET_PRIV (view);
1416
1417         if (G_STR_EMPTY (priv->filter_text)) {
1418                 return TRUE;
1419         }
1420         
1421         /* Check to see if iter matches any group names */
1422         gtk_tree_model_get (model, iter,
1423                             COL_IS_GROUP, &is_group,
1424                             COL_IS_SEPARATOR, &is_separator,
1425                             -1);
1426
1427         if (is_group) {
1428                 gchar *name;
1429
1430                 gtk_tree_model_get (model, iter, COL_NAME, &name, -1);
1431                 visible &= contact_list_view_filter_show_group (view,
1432                                                                 name,
1433                                                                 priv->filter_text);
1434                 g_free (name);
1435         } else if (is_separator) {
1436                 /* Do nothing here */
1437         } else {
1438                 EmpathyContact *contact;
1439
1440                 /* Check contact id */
1441                 gtk_tree_model_get (model, iter, COL_CONTACT, &contact, -1);
1442                 visible &= contact_list_view_filter_show_contact (contact, 
1443                                                                   priv->filter_text);
1444                 g_object_unref (contact);
1445         }
1446
1447         return visible;
1448 }
1449
1450 static void
1451 contact_list_view_action_cb (GtkAction             *action,
1452                              EmpathyContactListView *view)
1453 {
1454         EmpathyContactListViewPriv *priv;
1455         EmpathyContact             *contact;
1456         const gchar               *name;
1457         gchar                     *group;
1458         GtkWindow                 *parent;
1459
1460         priv = GET_PRIV (view);
1461
1462         name = gtk_action_get_name (action);
1463         if (!name) {
1464                 return;
1465         }
1466
1467         empathy_debug (DEBUG_DOMAIN, "Action:'%s' activated", name);
1468
1469         contact = empathy_contact_list_view_get_selected (view);
1470         group = empathy_contact_list_view_get_selected_group (view);
1471         parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1472
1473         if (contact && strcmp (name, "Chat") == 0) {
1474                 contact_list_view_action_activated (view, contact);
1475         }
1476         else if (contact && strcmp (name, "Information") == 0) {
1477                 empathy_contact_information_dialog_show (contact, parent, FALSE);
1478         }
1479         else if (contact && strcmp (name, "Edit") == 0) {
1480                 empathy_contact_information_dialog_show (contact, parent, TRUE);
1481         }
1482         else if (contact && strcmp (name, "Remove") == 0) {
1483                 /* FIXME: Ask for confirmation */
1484                 EmpathyContactList *list;
1485
1486                 list = empathy_contact_list_store_get_list_iface (priv->store);
1487                 empathy_contact_list_remove (list, contact,
1488                                              _("Sorry, I don't want you in my contact list anymore."));
1489         }
1490         else if (contact && strcmp (name, "Invite") == 0) {
1491         }
1492         else if (contact && strcmp (name, "SendFile") == 0) {
1493         }
1494         else if (contact && strcmp (name, "Log") == 0) {
1495                 empathy_log_window_show (empathy_contact_get_account (contact),
1496                                         empathy_contact_get_id (contact),
1497                                         FALSE,
1498                                         parent);
1499         }
1500         else if (group && strcmp (name, "Rename") == 0) {
1501         }
1502
1503         g_free (group);
1504         if (contact) {
1505                 g_object_unref (contact);
1506         }
1507 }
1508
1509 static void
1510 contact_list_view_action_activated (EmpathyContactListView *view,
1511                                     EmpathyContact         *contact)
1512 {
1513         MissionControl *mc;
1514
1515         mc = empathy_mission_control_new ();
1516         mission_control_request_channel (mc,
1517                                          empathy_contact_get_account (contact),
1518                                          TP_IFACE_CHANNEL_TYPE_TEXT,
1519                                          empathy_contact_get_handle (contact),
1520                                          TP_HANDLE_TYPE_CONTACT,
1521                                          NULL, NULL);
1522         g_object_unref (mc);
1523 }
1524