]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Use EmpathyLiveSearch in main window's contact list
[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-2008 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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  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-lib.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
33
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
36
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
43
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
53
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
56
57 /* Active users are those which have recently changed state
58  * (e.g. online, offline or from normal to a busy state).
59  */
60
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
62 typedef struct {
63         EmpathyContactListStore        *store;
64         GtkTreeRowReference            *drag_row;
65         EmpathyContactListFeatureFlags  list_features;
66         EmpathyContactFeatureFlags      contact_features;
67         GtkWidget                      *tooltip_widget;
68         GtkTargetList                  *file_targets;
69
70         GtkTreeModelFilter             *filter;
71         GtkWidget                      *search_widget;
72 } EmpathyContactListViewPriv;
73
74 typedef struct {
75         EmpathyContactListView *view;
76         GtkTreePath           *path;
77         guint                  timeout_id;
78 } DragMotionData;
79
80 typedef struct {
81         EmpathyContactListView *view;
82         EmpathyContact         *contact;
83         gboolean               remove;
84 } ShowActiveData;
85
86 enum {
87         PROP_0,
88         PROP_STORE,
89         PROP_LIST_FEATURES,
90         PROP_CONTACT_FEATURES,
91 };
92
93 enum DndDragType {
94         DND_DRAG_TYPE_CONTACT_ID,
95         DND_DRAG_TYPE_URI_LIST,
96         DND_DRAG_TYPE_STRING,
97 };
98
99 static const GtkTargetEntry drag_types_dest[] = {
100         { "text/path-list",  0, DND_DRAG_TYPE_URI_LIST },
101         { "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
102         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
103         { "text/plain",      0, DND_DRAG_TYPE_STRING },
104         { "STRING",          0, DND_DRAG_TYPE_STRING },
105 };
106
107 static const GtkTargetEntry drag_types_dest_file[] = {
108         { "text/path-list",  0, DND_DRAG_TYPE_URI_LIST },
109         { "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
110 };
111
112 static const GtkTargetEntry drag_types_source[] = {
113         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
114 };
115
116 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
117 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
118
119 enum {
120         DRAG_CONTACT_RECEIVED,
121         LAST_SIGNAL
122 };
123
124 static guint signals[LAST_SIGNAL];
125
126 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
127
128 static void
129 contact_list_view_tooltip_destroy_cb (GtkWidget              *widget,
130                                       EmpathyContactListView *view)
131 {
132         EmpathyContactListViewPriv *priv = GET_PRIV (view);
133
134         if (priv->tooltip_widget) {
135                 DEBUG ("Tooltip destroyed");
136                 g_object_unref (priv->tooltip_widget);
137                 priv->tooltip_widget = NULL;
138         }
139 }
140
141 static gboolean
142 contact_list_view_is_visible_contact (EmpathyContactListView *self,
143                                       EmpathyContact *contact)
144 {
145         EmpathyContactListViewPriv *priv = GET_PRIV (self);
146         EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
147         const gchar *str;
148
149         /* check alias name */
150         str = empathy_contact_get_name (contact);
151         if (empathy_live_search_match (live, str))
152                 return TRUE;
153
154         /* check contact id */
155         str = empathy_contact_get_id (contact);
156         if (empathy_live_search_match (live, str))
157                 return TRUE;
158
159         return FALSE;
160 }
161
162 static gboolean
163 contact_list_view_filter_visible_func (GtkTreeModel *model,
164                                        GtkTreeIter  *iter,
165                                        gpointer      user_data)
166 {
167         EmpathyContactListView     *self = EMPATHY_CONTACT_LIST_VIEW (user_data);
168         EmpathyContactListViewPriv *priv = GET_PRIV (self);
169         EmpathyContact             *contact = NULL;
170         gboolean                    is_group, is_separator, valid;
171         GtkTreeIter                 child_iter;
172         gboolean                    visible;
173
174         if (!gtk_widget_get_visible (priv->search_widget))
175                 return TRUE;
176
177         gtk_tree_model_get (model, iter,
178                 EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
179                 EMPATHY_CONTACT_LIST_STORE_COL_IS_SEPARATOR, &is_separator,
180                 EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
181                 -1);
182
183         if (contact != NULL) {
184                 visible = contact_list_view_is_visible_contact (self, contact);
185                 g_object_unref (contact);
186                 return visible;
187         }
188
189         if (is_separator) {
190                 return TRUE;
191         }
192
193         /* Not a contact, not a separator, must be a group */
194         g_return_val_if_fail (is_group, FALSE);
195
196         /* only show groups which are not empty */
197         for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
198              valid; valid = gtk_tree_model_iter_next (model, &child_iter)) {
199                 gtk_tree_model_get (model, &child_iter,
200                         EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
201                         -1);
202
203                 if (contact == NULL)
204                         continue;
205
206                 visible = contact_list_view_is_visible_contact (self, contact);
207                 g_object_unref (contact);
208
209                 /* show group if it has at least one visible contact in it */
210                 if (visible)
211                         return TRUE;
212         }
213
214         return FALSE;
215 }
216
217 static gboolean
218 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
219                                     gint                    x,
220                                     gint                    y,
221                                     gboolean                keyboard_mode,
222                                     GtkTooltip             *tooltip,
223                                     gpointer                user_data)
224 {
225         EmpathyContactListViewPriv *priv = GET_PRIV (view);
226         EmpathyContact             *contact;
227         GtkTreeModel               *model;
228         GtkTreeIter                 iter;
229         GtkTreePath                *path;
230         static gint                 running = 0;
231         gboolean                    ret = FALSE;
232
233         /* Avoid an infinite loop. See GNOME bug #574377 */
234         if (running > 0) {
235                 return FALSE;
236         }
237         running++;
238
239         /* Don't show the tooltip if there's already a popup menu */
240         if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL) {
241                 goto OUT;
242         }
243
244         if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
245                                                 keyboard_mode,
246                                                 &model, &path, &iter)) {
247                 goto OUT;
248         }
249
250         gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
251         gtk_tree_path_free (path);
252
253         gtk_tree_model_get (model, &iter,
254                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
255                             -1);
256         if (!contact) {
257                 goto OUT;
258         }
259
260         if (!priv->tooltip_widget) {
261                 priv->tooltip_widget = empathy_contact_widget_new (contact,
262                         EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
263                         EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
264                 gtk_container_set_border_width (
265                         GTK_CONTAINER (priv->tooltip_widget), 8);
266                 g_object_ref (priv->tooltip_widget);
267                 g_signal_connect (priv->tooltip_widget, "destroy",
268                                   G_CALLBACK (contact_list_view_tooltip_destroy_cb),
269                                   view);
270                 gtk_widget_show (priv->tooltip_widget);
271         } else {
272                 empathy_contact_widget_set_contact (priv->tooltip_widget,
273                                                     contact);
274         }
275
276         gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
277         ret = TRUE;
278
279         g_object_unref (contact);
280 OUT:
281         running--;
282
283         return ret;
284 }
285
286 typedef struct {
287         gchar *new_group;
288         gchar *old_group;
289         GdkDragAction action;
290 } DndGetContactData;
291
292 static void
293 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
294 {
295         g_free (data->new_group);
296         g_free (data->old_group);
297         g_slice_free (DndGetContactData, data);
298 }
299
300 static void
301 contact_list_view_drag_got_contact (TpConnection            *connection,
302                                     EmpathyContact          *contact,
303                                     const GError            *error,
304                                     gpointer                 user_data,
305                                     GObject                 *view)
306 {
307         EmpathyContactListViewPriv *priv = GET_PRIV (view);
308         DndGetContactData          *data = user_data;
309         EmpathyContactList         *list;
310
311         if (error != NULL) {
312                 DEBUG ("Error: %s", error->message);
313                 return;
314         }
315
316         DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
317                 empathy_contact_get_id (contact),
318                 empathy_contact_get_handle (contact),
319                 data->old_group, data->new_group);
320
321         list = empathy_contact_list_store_get_list_iface (priv->store);
322
323         if (!tp_strdiff (data->new_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
324                 /* Mark contact as favourite */
325                 empathy_contact_list_add_to_favourites (list, contact);
326                 return;
327         }
328
329         if (!tp_strdiff (data->old_group, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
330                 /* Remove contact as favourite */
331                 empathy_contact_list_remove_from_favourites (list, contact);
332                 /* Don't try to remove it */
333                 g_free (data->old_group);
334                 data->old_group = NULL;
335         }
336
337         if (data->new_group) {
338                 empathy_contact_list_add_to_group (list, contact, data->new_group);
339         }
340         if (data->old_group && data->action == GDK_ACTION_MOVE) {
341                 empathy_contact_list_remove_from_group (list, contact, data->old_group);
342         }
343 }
344
345 static gboolean
346 group_can_be_modified (const gchar *name,
347                        gboolean     is_fake_group,
348                        gboolean     adding)
349 {
350         /* Real groups can always be modified */
351         if (!is_fake_group)
352                 return TRUE;
353
354         /* The favorite fake group can be modified so users can
355          * add/remove favorites using DnD */
356         if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE))
357                 return TRUE;
358
359         /* We can remove contacts from the 'ungrouped' fake group */
360         if (!adding && !tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_UNGROUPED))
361                 return TRUE;
362
363         return FALSE;
364 }
365
366 static gboolean
367 contact_list_view_contact_drag_received (GtkWidget         *view,
368                                          GdkDragContext    *context,
369                                          GtkTreeModel      *model,
370                                          GtkTreePath       *path,
371                                          GtkSelectionData  *selection)
372 {
373         EmpathyContactListViewPriv *priv;
374         TpAccountManager           *account_manager;
375         TpConnection               *connection;
376         TpAccount                  *account;
377         DndGetContactData          *data;
378         GtkTreePath                *source_path;
379         const gchar   *sel_data;
380         gchar        **strv = NULL;
381         const gchar   *account_id = NULL;
382         const gchar   *contact_id = NULL;
383         gchar         *new_group = NULL;
384         gchar         *old_group = NULL;
385         gboolean       success = TRUE;
386         gboolean       new_group_is_fake, old_group_is_fake = TRUE;
387
388         priv = GET_PRIV (view);
389
390         sel_data = (const gchar *) gtk_selection_data_get_data (selection);
391         new_group = empathy_contact_list_store_get_parent_group (model,
392                                                                  path, NULL, &new_group_is_fake);
393
394         if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
395                 return FALSE;
396
397         /* Get source group information. */
398         if (priv->drag_row) {
399                 source_path = gtk_tree_row_reference_get_path (priv->drag_row);
400                 if (source_path) {
401                         old_group = empathy_contact_list_store_get_parent_group (
402                                                                                  model, source_path, NULL, &old_group_is_fake);
403                         gtk_tree_path_free (source_path);
404                 }
405         }
406
407         if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
408                 return FALSE;
409
410         if (!tp_strdiff (old_group, new_group)) {
411                 g_free (new_group);
412                 g_free (old_group);
413                 return FALSE;
414         }
415
416         account_manager = tp_account_manager_dup ();
417         strv = g_strsplit (sel_data, ":", 2);
418         if (g_strv_length (strv) == 2) {
419                 account_id = strv[0];
420                 contact_id = strv[1];
421                 account = tp_account_manager_ensure_account (account_manager, account_id);
422         }
423         if (account) {
424                 connection = tp_account_get_connection (account);
425         }
426
427         if (!connection) {
428                 DEBUG ("Failed to get connection for account '%s'", account_id);
429                 success = FALSE;
430                 g_free (new_group);
431                 g_free (old_group);
432                 g_object_unref (account_manager);
433                 return FALSE;
434         }
435
436         data = g_slice_new0 (DndGetContactData);
437         data->new_group = new_group;
438         data->old_group = old_group;
439         data->action = context->action;
440
441         /* FIXME: We should probably wait for the cb before calling
442          * gtk_drag_finish */
443         empathy_tp_contact_factory_get_from_id (connection, contact_id,
444                                                 contact_list_view_drag_got_contact,
445                                                 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
446                                                 G_OBJECT (view));
447         g_strfreev (strv);
448         g_object_unref (account_manager);
449
450         return TRUE;
451 }
452
453 static gboolean
454 contact_list_view_file_drag_received (GtkWidget         *view,
455                                       GdkDragContext    *context,
456                                       GtkTreeModel      *model,
457                                       GtkTreePath       *path,
458                                       GtkSelectionData  *selection)
459 {
460         GtkTreeIter     iter;
461         const gchar    *sel_data;
462         EmpathyContact *contact;
463
464         sel_data = (const gchar *) gtk_selection_data_get_data (selection);
465
466         gtk_tree_model_get_iter (model, &iter, path);
467         gtk_tree_model_get (model, &iter,
468                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
469                             -1);
470         if (!contact) {
471                 return FALSE;
472         }
473
474         empathy_send_file_from_uri_list (contact, sel_data);
475
476         g_object_unref (contact);
477
478         return TRUE;
479 }
480
481 static void
482 contact_list_view_drag_data_received (GtkWidget         *view,
483                                       GdkDragContext    *context,
484                                       gint               x,
485                                       gint               y,
486                                       GtkSelectionData  *selection,
487                                       guint              info,
488                                       guint              time_)
489 {
490         GtkTreeModel               *model;
491         gboolean                    is_row;
492         GtkTreeViewDropPosition     position;
493         GtkTreePath                *path;
494         gboolean                    success = TRUE;
495
496         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
497
498         /* Get destination group information. */
499         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
500                                                     x,
501                                                     y,
502                                                     &path,
503                                                     &position);
504         if (!is_row) {
505                 success = FALSE;
506         }
507         else if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
508                 success = contact_list_view_contact_drag_received (view,
509                                                                    context,
510                                                                    model,
511                                                                    path,
512                                                                    selection);
513         }
514         else if (info == DND_DRAG_TYPE_URI_LIST) {
515                 success = contact_list_view_file_drag_received (view,
516                                                                 context,
517                                                                 model,
518                                                                 path,
519                                                                 selection);
520         }
521
522         gtk_tree_path_free (path);
523         gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
524 }
525
526 static gboolean
527 contact_list_view_drag_motion_cb (DragMotionData *data)
528 {
529         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
530                                   data->path,
531                                   FALSE);
532
533         data->timeout_id = 0;
534
535         return FALSE;
536 }
537
538 static gboolean
539 contact_list_view_drag_motion (GtkWidget      *widget,
540                                GdkDragContext *context,
541                                gint            x,
542                                gint            y,
543                                guint           time_)
544 {
545         EmpathyContactListViewPriv *priv;
546         GtkTreeModel               *model;
547         GdkAtom                target;
548         GtkTreeIter            iter;
549         static DragMotionData *dm = NULL;
550         GtkTreePath           *path;
551         gboolean               is_row;
552         gboolean               is_different = FALSE;
553         gboolean               cleanup = TRUE;
554         gboolean               retval = TRUE;
555
556         priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
557         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
558
559         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
560                                                 x,
561                                                 y,
562                                                 &path,
563                                                 NULL,
564                                                 NULL,
565                                                 NULL);
566
567         cleanup &= (!dm);
568
569         if (is_row) {
570                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
571                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
572         } else {
573                 cleanup &= FALSE;
574         }
575
576         if (path == NULL) {
577                 /* Coordinates don't point to an actual row, so make sure the pointer
578                    and highlighting don't indicate that a drag is possible.
579                  */
580                 gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
581                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
582                 return FALSE;
583         }
584         target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
585         gtk_tree_model_get_iter (model, &iter, path);
586
587         if (target == GDK_NONE) {
588                 /* If target == GDK_NONE, then we don't have a target that can be
589                    dropped on a contact.  This means a contact drag.  If we're
590                    pointing to a group, highlight it.  Otherwise, if the contact
591                    we're pointing to is in a group, highlight that.  Otherwise,
592                    set the drag position to before the first row for a drag into
593                    the "non-group" at the top.
594                  */
595                 GtkTreeIter  group_iter;
596                 gboolean     is_group;
597                 GtkTreePath *group_path;
598                 gtk_tree_model_get (model, &iter,
599                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
600                                     -1);
601                 if (is_group) {
602                         group_iter = iter;
603                 }
604                 else {
605                         if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
606                                 gtk_tree_model_get (model, &group_iter,
607                                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
608                                                     -1);
609                 }
610                 if (is_group) {
611                         gdk_drag_status (context, GDK_ACTION_MOVE, time_);
612                         group_path = gtk_tree_model_get_path (model, &group_iter);
613                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
614                                                          group_path,
615                                                          GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
616                         gtk_tree_path_free (group_path);
617                 }
618                 else {
619                         group_path = gtk_tree_path_new_first ();
620                         gdk_drag_status (context, GDK_ACTION_MOVE, time_);
621                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
622                                                          group_path,
623                                                          GTK_TREE_VIEW_DROP_BEFORE);
624                 }
625         }
626         else {
627                 /* This is a file drag, and it can only be dropped on contacts,
628                    not groups.
629                  */
630                 EmpathyContact *contact;
631                 gtk_tree_model_get (model, &iter,
632                                     EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
633                                     -1);
634                 if (contact != NULL &&
635                     empathy_contact_is_online (contact) &&
636                     (empathy_contact_get_capabilities (contact) & EMPATHY_CAPABILITIES_FT)) {
637                         gdk_drag_status (context, GDK_ACTION_COPY, time_);
638                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
639                                                          path,
640                                                          GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
641                         g_object_unref (contact);
642                 }
643                 else {
644                         gdk_drag_status (context, 0, time_);
645                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
646                         retval = FALSE;
647                 }
648         }
649
650         if (!is_different && !cleanup) {
651                 return retval;
652         }
653
654         if (dm) {
655                 gtk_tree_path_free (dm->path);
656                 if (dm->timeout_id) {
657                         g_source_remove (dm->timeout_id);
658                 }
659
660                 g_free (dm);
661
662                 dm = NULL;
663         }
664
665         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
666                 dm = g_new0 (DragMotionData, 1);
667
668                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
669                 dm->path = gtk_tree_path_copy (path);
670
671                 dm->timeout_id = g_timeout_add_seconds (1,
672                         (GSourceFunc) contact_list_view_drag_motion_cb,
673                         dm);
674         }
675
676         return retval;
677 }
678
679 static void
680 contact_list_view_drag_begin (GtkWidget      *widget,
681                               GdkDragContext *context)
682 {
683         EmpathyContactListViewPriv *priv;
684         GtkTreeSelection          *selection;
685         GtkTreeModel              *model;
686         GtkTreePath               *path;
687         GtkTreeIter                iter;
688
689         priv = GET_PRIV (widget);
690
691         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
692                                                                               context);
693
694         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
695         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
696                 return;
697         }
698
699         path = gtk_tree_model_get_path (model, &iter);
700         priv->drag_row = gtk_tree_row_reference_new (model, path);
701         gtk_tree_path_free (path);
702 }
703
704 static void
705 contact_list_view_drag_data_get (GtkWidget        *widget,
706                                  GdkDragContext   *context,
707                                  GtkSelectionData *selection,
708                                  guint             info,
709                                  guint             time_)
710 {
711         EmpathyContactListViewPriv *priv;
712         GtkTreePath                *src_path;
713         GtkTreeIter                 iter;
714         GtkTreeModel               *model;
715         EmpathyContact             *contact;
716         TpAccount                  *account;
717         const gchar                *contact_id;
718         const gchar                *account_id;
719         gchar                      *str;
720
721         priv = GET_PRIV (widget);
722
723         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
724         if (!priv->drag_row) {
725                 return;
726         }
727
728         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
729         if (!src_path) {
730                 return;
731         }
732
733         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
734                 gtk_tree_path_free (src_path);
735                 return;
736         }
737
738         gtk_tree_path_free (src_path);
739
740         contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
741         if (!contact) {
742                 return;
743         }
744
745         account = empathy_contact_get_account (contact);
746         account_id = tp_proxy_get_object_path (account);
747         contact_id = empathy_contact_get_id (contact);
748         g_object_unref (contact);
749         str = g_strconcat (account_id, ":", contact_id, NULL);
750
751         switch (info) {
752         case DND_DRAG_TYPE_CONTACT_ID:
753                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
754                                         (guchar *) str, strlen (str) + 1);
755                 break;
756         }
757
758         g_free (str);
759 }
760
761 static void
762 contact_list_view_drag_end (GtkWidget      *widget,
763                             GdkDragContext *context)
764 {
765         EmpathyContactListViewPriv *priv;
766
767         priv = GET_PRIV (widget);
768
769         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
770                                                                             context);
771
772         if (priv->drag_row) {
773                 gtk_tree_row_reference_free (priv->drag_row);
774                 priv->drag_row = NULL;
775         }
776 }
777
778 static gboolean
779 contact_list_view_drag_drop (GtkWidget      *widget,
780                              GdkDragContext *drag_context,
781                              gint            x,
782                              gint            y,
783                              guint           time_)
784 {
785         return FALSE;
786 }
787
788 typedef struct {
789         EmpathyContactListView *view;
790         guint                   button;
791         guint32                 time;
792 } MenuPopupData;
793
794 static gboolean
795 contact_list_view_popup_menu_idle_cb (gpointer user_data)
796 {
797         MenuPopupData *data = user_data;
798         GtkWidget     *menu;
799
800         menu = empathy_contact_list_view_get_contact_menu (data->view);
801         if (!menu) {
802                 menu = empathy_contact_list_view_get_group_menu (data->view);
803         }
804
805         if (menu) {
806                 g_signal_connect (menu, "deactivate",
807                                   G_CALLBACK (gtk_menu_detach), NULL);
808                 gtk_menu_attach_to_widget (GTK_MENU (menu),
809                                            GTK_WIDGET (data->view), NULL);
810                 gtk_widget_show (menu);
811                 gtk_menu_popup (GTK_MENU (menu),
812                                 NULL, NULL, NULL, NULL,
813                                 data->button, data->time);
814                 g_object_ref_sink (menu);
815                 g_object_unref (menu);
816         }
817
818         g_slice_free (MenuPopupData, data);
819
820         return FALSE;
821 }
822
823 static gboolean
824 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
825                                          GdkEventButton         *event,
826                                          gpointer                user_data)
827 {
828         if (event->button == 3) {
829                 MenuPopupData *data;
830
831                 data = g_slice_new (MenuPopupData);
832                 data->view = view;
833                 data->button = event->button;
834                 data->time = event->time;
835                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
836         }
837
838         return FALSE;
839 }
840
841 static gboolean
842 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
843                                       GdkEventKey            *event,
844                                       gpointer                user_data)
845 {
846         if (event->keyval == GDK_Menu) {
847                 MenuPopupData *data;
848
849                 data = g_slice_new (MenuPopupData);
850                 data->view = view;
851                 data->button = 0;
852                 data->time = event->time;
853                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
854         }
855
856         return FALSE;
857 }
858
859 static void
860 contact_list_view_row_activated (GtkTreeView       *view,
861                                  GtkTreePath       *path,
862                                  GtkTreeViewColumn *column)
863 {
864         EmpathyContactListViewPriv *priv = GET_PRIV (view);
865         EmpathyContact             *contact;
866         GtkTreeModel               *model;
867         GtkTreeIter                 iter;
868
869         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
870                 return;
871         }
872
873         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
874         gtk_tree_model_get_iter (model, &iter, path);
875         gtk_tree_model_get (model, &iter,
876                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
877                             -1);
878
879         if (contact) {
880                 DEBUG ("Starting a chat");
881                 empathy_dispatcher_chat_with_contact (contact,
882                         gtk_get_current_event_time (), NULL, NULL);
883                 g_object_unref (contact);
884         }
885 }
886
887 static void
888 contact_list_view_call_activated_cb (
889     EmpathyCellRendererActivatable *cell,
890     const gchar                    *path_string,
891     EmpathyContactListView         *view)
892 {
893         GtkWidget *menu;
894         GtkTreeModel *model;
895         GtkTreeIter iter;
896         EmpathyContact *contact;
897         GdkEventButton *event;
898         GtkMenuShell *shell;
899         GtkWidget *item;
900
901         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
902         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
903                 return;
904
905         gtk_tree_model_get (model, &iter,
906                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
907                             -1);
908         if (contact == NULL)
909                 return;
910
911         event = (GdkEventButton *) gtk_get_current_event ();
912
913         menu = gtk_menu_new ();
914         shell = GTK_MENU_SHELL (menu);
915
916         /* audio */
917         item = empathy_contact_audio_call_menu_item_new (contact);
918         gtk_menu_shell_append (shell, item);
919         gtk_widget_show (item);
920
921         /* video */
922         item = empathy_contact_video_call_menu_item_new (contact);
923         gtk_menu_shell_append (shell, item);
924         gtk_widget_show (item);
925
926         g_signal_connect (menu, "deactivate",
927                           G_CALLBACK (gtk_menu_detach), NULL);
928         gtk_menu_attach_to_widget (GTK_MENU (menu),
929                                    GTK_WIDGET (view), NULL);
930         gtk_widget_show (menu);
931         gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
932                         event->button, event->time);
933         g_object_ref_sink (menu);
934         g_object_unref (menu);
935
936         g_object_unref (contact);
937 }
938
939 static void
940 contact_list_view_cell_set_background (EmpathyContactListView *view,
941                                        GtkCellRenderer        *cell,
942                                        gboolean                is_group,
943                                        gboolean                is_active)
944 {
945         GdkColor  color;
946         GtkStyle *style;
947
948         style = gtk_widget_get_style (GTK_WIDGET (view));
949
950         if (!is_group && is_active) {
951                 color = style->bg[GTK_STATE_SELECTED];
952
953                 /* Here we take the current theme colour and add it to
954                  * the colour for white and average the two. This
955                  * gives a colour which is inline with the theme but
956                  * slightly whiter.
957                  */
958                 color.red = (color.red + (style->white).red) / 2;
959                 color.green = (color.green + (style->white).green) / 2;
960                 color.blue = (color.blue + (style->white).blue) / 2;
961
962                 g_object_set (cell,
963                               "cell-background-gdk", &color,
964                               NULL);
965         } else {
966                 g_object_set (cell,
967                               "cell-background-gdk", NULL,
968                               NULL);
969         }
970 }
971
972 static void
973 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn      *tree_column,
974                                          GtkCellRenderer        *cell,
975                                          GtkTreeModel           *model,
976                                          GtkTreeIter            *iter,
977                                          EmpathyContactListView *view)
978 {
979         GdkPixbuf *pixbuf;
980         gboolean   is_group;
981         gboolean   is_active;
982
983         gtk_tree_model_get (model, iter,
984                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
985                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
986                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
987                             -1);
988
989         g_object_set (cell,
990                       "visible", !is_group,
991                       "pixbuf", pixbuf,
992                       NULL);
993
994         if (pixbuf != NULL) {
995                 g_object_unref (pixbuf);
996         }
997
998         contact_list_view_cell_set_background (view, cell, is_group, is_active);
999 }
1000
1001 static void
1002 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn     *tree_column,
1003                                              GtkCellRenderer       *cell,
1004                                              GtkTreeModel          *model,
1005                                              GtkTreeIter           *iter,
1006                                              EmpathyContactListView *view)
1007 {
1008         GdkPixbuf *pixbuf = NULL;
1009         gboolean is_group;
1010         gchar *name;
1011
1012         gtk_tree_model_get (model, iter,
1013                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1014                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1015                             -1);
1016
1017         if (!is_group)
1018                 goto out;
1019
1020         if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1021                 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1022                         GTK_ICON_SIZE_MENU);
1023         }
1024         else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1025                 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1026                         GTK_ICON_SIZE_MENU);
1027         }
1028
1029 out:
1030         g_object_set (cell,
1031                       "visible", pixbuf != NULL,
1032                       "pixbuf", pixbuf,
1033                       NULL);
1034
1035         if (pixbuf != NULL)
1036                 g_object_unref (pixbuf);
1037
1038         g_free (name);
1039 }
1040
1041 static void
1042 contact_list_view_audio_call_cell_data_func (
1043                                        GtkTreeViewColumn      *tree_column,
1044                                        GtkCellRenderer        *cell,
1045                                        GtkTreeModel           *model,
1046                                        GtkTreeIter            *iter,
1047                                        EmpathyContactListView *view)
1048 {
1049         gboolean is_group;
1050         gboolean is_active;
1051         gboolean can_audio, can_video;
1052
1053         gtk_tree_model_get (model, iter,
1054                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1055                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1056                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1057                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1058                             -1);
1059
1060         g_object_set (cell,
1061                       "visible", !is_group && (can_audio || can_video),
1062                       "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1063                       NULL);
1064
1065         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1066 }
1067
1068 static void
1069 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn      *tree_column,
1070                                          GtkCellRenderer        *cell,
1071                                          GtkTreeModel           *model,
1072                                          GtkTreeIter            *iter,
1073                                          EmpathyContactListView *view)
1074 {
1075         GdkPixbuf *pixbuf;
1076         gboolean   show_avatar;
1077         gboolean   is_group;
1078         gboolean   is_active;
1079
1080         gtk_tree_model_get (model, iter,
1081                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1082                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1083                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1084                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1085                             -1);
1086
1087         g_object_set (cell,
1088                       "visible", !is_group && show_avatar,
1089                       "pixbuf", pixbuf,
1090                       NULL);
1091
1092         if (pixbuf) {
1093                 g_object_unref (pixbuf);
1094         }
1095
1096         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1097 }
1098
1099 static void
1100 contact_list_view_text_cell_data_func (GtkTreeViewColumn      *tree_column,
1101                                        GtkCellRenderer        *cell,
1102                                        GtkTreeModel           *model,
1103                                        GtkTreeIter            *iter,
1104                                        EmpathyContactListView *view)
1105 {
1106         gboolean is_group;
1107         gboolean is_active;
1108
1109         gtk_tree_model_get (model, iter,
1110                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1111                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1112                             -1);
1113
1114         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1115 }
1116
1117 static void
1118 contact_list_view_expander_cell_data_func (GtkTreeViewColumn      *column,
1119                                            GtkCellRenderer        *cell,
1120                                            GtkTreeModel           *model,
1121                                            GtkTreeIter            *iter,
1122                                            EmpathyContactListView *view)
1123 {
1124         gboolean is_group;
1125         gboolean is_active;
1126
1127         gtk_tree_model_get (model, iter,
1128                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1129                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1130                             -1);
1131
1132         if (gtk_tree_model_iter_has_child (model, iter)) {
1133                 GtkTreePath *path;
1134                 gboolean     row_expanded;
1135
1136                 path = gtk_tree_model_get_path (model, iter);
1137                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1138                 gtk_tree_path_free (path);
1139
1140                 g_object_set (cell,
1141                               "visible", TRUE,
1142                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1143                               NULL);
1144         } else {
1145                 g_object_set (cell, "visible", FALSE, NULL);
1146         }
1147
1148         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1149 }
1150
1151 static void
1152 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1153                                              GtkTreeIter            *iter,
1154                                              GtkTreePath            *path,
1155                                              gpointer                user_data)
1156 {
1157         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1158         GtkTreeModel               *model;
1159         gchar                      *name;
1160         gboolean                    expanded;
1161
1162         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1163                 return;
1164         }
1165
1166         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1167
1168         gtk_tree_model_get (model, iter,
1169                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1170                             -1);
1171
1172         expanded = GPOINTER_TO_INT (user_data);
1173         empathy_contact_group_set_expanded (name, expanded);
1174
1175         g_free (name);
1176 }
1177
1178 static gboolean
1179 contact_list_view_start_search_cb (EmpathyContactListView *view,
1180                                    gpointer                data)
1181 {
1182         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1183
1184         if (priv->search_widget == NULL)
1185                 return FALSE;
1186
1187         if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1188                 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1189         else
1190                 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1191
1192         return TRUE;
1193 }
1194
1195 static void
1196 contact_list_view_search_text_notify_cb (EmpathyLiveSearch      *search,
1197                                          GParamSpec             *pspec,
1198                                          EmpathyContactListView *view)
1199 {
1200         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1201
1202         gtk_tree_model_filter_refilter (priv->filter);
1203 }
1204
1205 static void
1206 contact_list_view_search_hide_cb (EmpathyLiveSearch      *search,
1207                                   EmpathyContactListView *view)
1208 {
1209         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1210         GtkTreeModel               *model;
1211         GtkTreeIter                 iter;
1212         gboolean                    valid = FALSE;
1213
1214         /* block expand or collapse handlers, they would write the
1215          * expand or collapsed setting to file otherwise */
1216         g_signal_handlers_block_by_func (view,
1217                 contact_list_view_row_expand_or_collapse_cb,
1218                 GINT_TO_POINTER (TRUE));
1219         g_signal_handlers_block_by_func (view,
1220                 contact_list_view_row_expand_or_collapse_cb,
1221                 GINT_TO_POINTER (FALSE));
1222
1223         /* restore which groups are expanded and which are not */
1224         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1225         for (valid = gtk_tree_model_get_iter_first (model, &iter);
1226              valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1227                 gboolean      is_group;
1228                 gchar        *name = NULL;
1229                 GtkTreePath  *path;
1230
1231                 gtk_tree_model_get (model, &iter,
1232                         EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1233                         EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1234                         -1);
1235
1236                 if (!is_group) {
1237                         g_free (name);
1238                         continue;
1239                 }
1240
1241                 path = gtk_tree_model_get_path (model, &iter);
1242                 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1243                     empathy_contact_group_get_expanded (name)) {
1244                         gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1245                                 TRUE);
1246                 } else {
1247                         gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1248                 }
1249
1250                 gtk_tree_path_free(path);
1251                 g_free (name);
1252         }
1253
1254         /* unblock expand or collapse handlers */
1255         g_signal_handlers_unblock_by_func (view,
1256                 contact_list_view_row_expand_or_collapse_cb,
1257                 GINT_TO_POINTER (TRUE));
1258         g_signal_handlers_unblock_by_func (view,
1259                 contact_list_view_row_expand_or_collapse_cb,
1260                 GINT_TO_POINTER (FALSE));
1261 }
1262
1263 static void
1264 contact_list_view_search_show_cb (EmpathyLiveSearch      *search,
1265                                   EmpathyContactListView *view)
1266 {
1267         /* block expand or collapse handlers during expand all, they would
1268          * write the expand or collapsed setting to file otherwise */
1269         g_signal_handlers_block_by_func (view,
1270                 contact_list_view_row_expand_or_collapse_cb,
1271                 GINT_TO_POINTER (TRUE));
1272
1273         gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1274
1275         g_signal_handlers_unblock_by_func (view,
1276                 contact_list_view_row_expand_or_collapse_cb,
1277                 GINT_TO_POINTER (TRUE));
1278 }
1279
1280 typedef struct {
1281         EmpathyContactListView *view;
1282         GtkTreePath *path;
1283         gboolean expand;
1284 } ExpandData;
1285
1286 static gboolean
1287 contact_list_view_expand_idle_cb (gpointer user_data)
1288 {
1289         ExpandData *data = user_data;
1290
1291         g_signal_handlers_block_by_func (data->view,
1292                 contact_list_view_row_expand_or_collapse_cb,
1293                 GINT_TO_POINTER (data->expand));
1294
1295         if (data->expand) {
1296                 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
1297                         data->path, TRUE);
1298         } else {
1299                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view),
1300                         data->path);
1301         }
1302
1303         g_signal_handlers_unblock_by_func (data->view,
1304                 contact_list_view_row_expand_or_collapse_cb,
1305                 GINT_TO_POINTER (data->expand));
1306
1307         g_object_unref (data->view);
1308         gtk_tree_path_free (data->path);
1309         g_slice_free (ExpandData, data);
1310
1311         return FALSE;
1312 }
1313
1314 static void
1315 contact_list_view_row_has_child_toggled_cb (GtkTreeModel           *model,
1316                                             GtkTreePath            *path,
1317                                             GtkTreeIter            *iter,
1318                                             EmpathyContactListView *view)
1319 {
1320         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1321         gboolean  is_group = FALSE;
1322         gchar    *name = NULL;
1323         ExpandData *data;
1324
1325         gtk_tree_model_get (model, iter,
1326                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1327                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1328                             -1);
1329
1330         if (!is_group || EMP_STR_EMPTY (name)) {
1331                 g_free (name);
1332                 return;
1333         }
1334
1335         data = g_slice_new0 (ExpandData);
1336         data->view = g_object_ref (view);
1337         data->path = gtk_tree_path_copy (path);
1338         data->expand =
1339                 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1340                 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1341                 empathy_contact_group_get_expanded (name);
1342
1343         /* FIXME: It doesn't work to call gtk_tree_view_expand_row() from within
1344          * gtk_tree_model_filter_refilter() */
1345         g_idle_add (contact_list_view_expand_idle_cb, data);
1346
1347         g_free (name);
1348 }
1349
1350 static void
1351 contact_list_view_constructed (GObject *object)
1352 {
1353         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1354         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1355         GtkCellRenderer            *cell;
1356         GtkTreeViewColumn          *col;
1357         guint                       i;
1358
1359         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1360                                              empathy_contact_list_store_search_equal_func,
1361                                              NULL, NULL);
1362
1363         priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1364                         GTK_TREE_MODEL (priv->store), NULL));
1365         gtk_tree_model_filter_set_visible_func (priv->filter,
1366                         contact_list_view_filter_visible_func,
1367                         view, NULL);
1368
1369         g_signal_connect (priv->filter, "row-has-child-toggled",
1370                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1371                           view);
1372
1373         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1374                                  GTK_TREE_MODEL (priv->filter));
1375
1376         /* Setup view */
1377         /* Setting reorderable is a hack that gets us row previews as drag icons
1378            for free.  We override all the drag handlers.  It's tricky to get the
1379            position of the drag icon right in drag_begin.  GtkTreeView has special
1380            voodoo for it, so we let it do the voodoo that he do.
1381          */
1382         g_object_set (view,
1383                       "headers-visible", FALSE,
1384                       "reorderable", TRUE,
1385                       "show-expanders", FALSE,
1386                       NULL);
1387
1388         col = gtk_tree_view_column_new ();
1389
1390         /* State */
1391         cell = gtk_cell_renderer_pixbuf_new ();
1392         gtk_tree_view_column_pack_start (col, cell, FALSE);
1393         gtk_tree_view_column_set_cell_data_func (
1394                 col, cell,
1395                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1396                 view, NULL);
1397
1398         g_object_set (cell,
1399                       "xpad", 5,
1400                       "ypad", 1,
1401                       "visible", FALSE,
1402                       NULL);
1403
1404         /* Group icon */
1405         cell = gtk_cell_renderer_pixbuf_new ();
1406         gtk_tree_view_column_pack_start (col, cell, FALSE);
1407         gtk_tree_view_column_set_cell_data_func (
1408                 col, cell,
1409                 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1410                 view, NULL);
1411
1412         g_object_set (cell,
1413                       "xpad", 0,
1414                       "ypad", 0,
1415                       "visible", FALSE,
1416                       "width", 16,
1417                       "height", 16,
1418                       NULL);
1419
1420         /* Name */
1421         cell = empathy_cell_renderer_text_new ();
1422         gtk_tree_view_column_pack_start (col, cell, TRUE);
1423         gtk_tree_view_column_set_cell_data_func (
1424                 col, cell,
1425                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1426                 view, NULL);
1427
1428         gtk_tree_view_column_add_attribute (col, cell,
1429                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1430         gtk_tree_view_column_add_attribute (col, cell,
1431                                             "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1432         gtk_tree_view_column_add_attribute (col, cell,
1433                                             "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1434         gtk_tree_view_column_add_attribute (col, cell,
1435                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1436         gtk_tree_view_column_add_attribute (col, cell,
1437                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1438         gtk_tree_view_column_add_attribute (col, cell,
1439                                             "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1440
1441         /* Audio Call Icon */
1442         cell = empathy_cell_renderer_activatable_new ();
1443         gtk_tree_view_column_pack_start (col, cell, FALSE);
1444         gtk_tree_view_column_set_cell_data_func (
1445                 col, cell,
1446                 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1447                 view, NULL);
1448
1449         g_object_set (cell,
1450                       "visible", FALSE,
1451                       NULL);
1452
1453         g_signal_connect (cell, "path-activated",
1454                           G_CALLBACK (contact_list_view_call_activated_cb),
1455                           view);
1456
1457         /* Avatar */
1458         cell = gtk_cell_renderer_pixbuf_new ();
1459         gtk_tree_view_column_pack_start (col, cell, FALSE);
1460         gtk_tree_view_column_set_cell_data_func (
1461                 col, cell,
1462                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1463                 view, NULL);
1464
1465         g_object_set (cell,
1466                       "xpad", 0,
1467                       "ypad", 0,
1468                       "visible", FALSE,
1469                       "width", 32,
1470                       "height", 32,
1471                       NULL);
1472
1473         /* Expander */
1474         cell = empathy_cell_renderer_expander_new ();
1475         gtk_tree_view_column_pack_end (col, cell, FALSE);
1476         gtk_tree_view_column_set_cell_data_func (
1477                 col, cell,
1478                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1479                 view, NULL);
1480
1481         /* Actually add the column now we have added all cell renderers */
1482         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1483
1484         /* Drag & Drop. */
1485         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1486                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1487                                                       FALSE);
1488         }
1489
1490         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1491                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1492                                                         FALSE);
1493         }
1494 }
1495
1496 static void
1497 contact_list_view_set_list_features (EmpathyContactListView         *view,
1498                                      EmpathyContactListFeatureFlags  features)
1499 {
1500         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1501         gboolean                    has_tooltip;
1502
1503         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1504
1505         priv->list_features = features;
1506
1507         /* Update DnD source/dest */
1508         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1509                 gtk_drag_source_set (GTK_WIDGET (view),
1510                                      GDK_BUTTON1_MASK,
1511                                      drag_types_source,
1512                                      G_N_ELEMENTS (drag_types_source),
1513                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1514         } else {
1515                 gtk_drag_source_unset (GTK_WIDGET (view));
1516
1517         }
1518
1519         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1520                 gtk_drag_dest_set (GTK_WIDGET (view),
1521                                    GTK_DEST_DEFAULT_ALL,
1522                                    drag_types_dest,
1523                                    G_N_ELEMENTS (drag_types_dest),
1524                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1525         } else {
1526                 /* FIXME: URI could still be droped depending on FT feature */
1527                 gtk_drag_dest_unset (GTK_WIDGET (view));
1528         }
1529
1530         /* Update has-tooltip */
1531         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1532         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1533 }
1534
1535 static void
1536 contact_list_view_dispose (GObject *object)
1537 {
1538         EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1539         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1540
1541         if (priv->store) {
1542                 g_object_unref (priv->store);
1543                 priv->store = NULL;
1544         }
1545         if (priv->filter) {
1546                 g_object_unref (priv->filter);
1547                 priv->filter = NULL;
1548         }
1549         if (priv->tooltip_widget) {
1550                 gtk_widget_destroy (priv->tooltip_widget);
1551                 priv->tooltip_widget = NULL;
1552         }
1553         if (priv->file_targets) {
1554                 gtk_target_list_unref (priv->file_targets);
1555                 priv->file_targets = NULL;
1556         }
1557
1558         empathy_contact_list_view_set_live_search (view, NULL);
1559
1560         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1561 }
1562
1563 static void
1564 contact_list_view_get_property (GObject    *object,
1565                                 guint       param_id,
1566                                 GValue     *value,
1567                                 GParamSpec *pspec)
1568 {
1569         EmpathyContactListViewPriv *priv;
1570
1571         priv = GET_PRIV (object);
1572
1573         switch (param_id) {
1574         case PROP_STORE:
1575                 g_value_set_object (value, priv->store);
1576                 break;
1577         case PROP_LIST_FEATURES:
1578                 g_value_set_flags (value, priv->list_features);
1579                 break;
1580         case PROP_CONTACT_FEATURES:
1581                 g_value_set_flags (value, priv->contact_features);
1582                 break;
1583         default:
1584                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1585                 break;
1586         };
1587 }
1588
1589 static void
1590 contact_list_view_set_property (GObject      *object,
1591                                 guint         param_id,
1592                                 const GValue *value,
1593                                 GParamSpec   *pspec)
1594 {
1595         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1596         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1597
1598         switch (param_id) {
1599         case PROP_STORE:
1600                 priv->store = g_value_dup_object (value);
1601                 break;
1602         case PROP_LIST_FEATURES:
1603                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1604                 break;
1605         case PROP_CONTACT_FEATURES:
1606                 priv->contact_features = g_value_get_flags (value);
1607                 break;
1608         default:
1609                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1610                 break;
1611         };
1612 }
1613
1614 static void
1615 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1616 {
1617         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1618         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1619         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1620
1621         object_class->constructed        = contact_list_view_constructed;
1622         object_class->dispose            = contact_list_view_dispose;
1623         object_class->get_property       = contact_list_view_get_property;
1624         object_class->set_property       = contact_list_view_set_property;
1625
1626         widget_class->drag_data_received = contact_list_view_drag_data_received;
1627         widget_class->drag_drop          = contact_list_view_drag_drop;
1628         widget_class->drag_begin         = contact_list_view_drag_begin;
1629         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1630         widget_class->drag_end           = contact_list_view_drag_end;
1631         widget_class->drag_motion        = contact_list_view_drag_motion;
1632
1633         /* We use the class method to let user of this widget to connect to
1634          * the signal and stop emission of the signal so the default handler
1635          * won't be called. */
1636         tree_view_class->row_activated = contact_list_view_row_activated;
1637
1638         signals[DRAG_CONTACT_RECEIVED] =
1639                 g_signal_new ("drag-contact-received",
1640                               G_OBJECT_CLASS_TYPE (klass),
1641                               G_SIGNAL_RUN_LAST,
1642                               0,
1643                               NULL, NULL,
1644                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1645                               G_TYPE_NONE,
1646                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1647
1648         g_object_class_install_property (object_class,
1649                                          PROP_STORE,
1650                                          g_param_spec_object ("store",
1651                                                              "The store of the view",
1652                                                              "The store of the view",
1653                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1654                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1655         g_object_class_install_property (object_class,
1656                                          PROP_LIST_FEATURES,
1657                                          g_param_spec_flags ("list-features",
1658                                                              "Features of the view",
1659                                                              "Flags for all enabled features",
1660                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1661                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1662                                                               G_PARAM_READWRITE));
1663         g_object_class_install_property (object_class,
1664                                          PROP_CONTACT_FEATURES,
1665                                          g_param_spec_flags ("contact-features",
1666                                                              "Features of the contact menu",
1667                                                              "Flags for all enabled features for the menu",
1668                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1669                                                               EMPATHY_CONTACT_FEATURE_NONE,
1670                                                               G_PARAM_READWRITE));
1671
1672         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1673 }
1674
1675 static void
1676 empathy_contact_list_view_init (EmpathyContactListView *view)
1677 {
1678         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1679                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1680
1681         view->priv = priv;
1682
1683         /* Get saved group states. */
1684         empathy_contact_groups_get_all ();
1685
1686         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1687                                               empathy_contact_list_store_row_separator_func,
1688                                               NULL, NULL);
1689
1690         /* Set up drag target lists. */
1691         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1692                                                   G_N_ELEMENTS (drag_types_dest_file));
1693
1694         /* Connect to tree view signals rather than override. */
1695         g_signal_connect (view, "button-press-event",
1696                           G_CALLBACK (contact_list_view_button_press_event_cb),
1697                           NULL);
1698         g_signal_connect (view, "key-press-event",
1699                           G_CALLBACK (contact_list_view_key_press_event_cb),
1700                           NULL);
1701         g_signal_connect (view, "row-expanded",
1702                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1703                           GINT_TO_POINTER (TRUE));
1704         g_signal_connect (view, "row-collapsed",
1705                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1706                           GINT_TO_POINTER (FALSE));
1707         g_signal_connect (view, "query-tooltip",
1708                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1709                           NULL);
1710 }
1711
1712 EmpathyContactListView *
1713 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1714                                EmpathyContactListFeatureFlags  list_features,
1715                                EmpathyContactFeatureFlags      contact_features)
1716 {
1717         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1718
1719         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1720                              "store", store,
1721                              "contact-features", contact_features,
1722                              "list-features", list_features,
1723                              NULL);
1724 }
1725
1726 EmpathyContact *
1727 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1728 {
1729         EmpathyContactListViewPriv *priv;
1730         GtkTreeSelection          *selection;
1731         GtkTreeIter                iter;
1732         GtkTreeModel              *model;
1733         EmpathyContact             *contact;
1734
1735         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1736
1737         priv = GET_PRIV (view);
1738
1739         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1740         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1741                 return NULL;
1742         }
1743
1744         gtk_tree_model_get (model, &iter,
1745                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1746                             -1);
1747
1748         return contact;
1749 }
1750
1751 EmpathyContactListFlags
1752 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1753 {
1754         EmpathyContactListViewPriv *priv;
1755         GtkTreeSelection          *selection;
1756         GtkTreeIter                iter;
1757         GtkTreeModel              *model;
1758         EmpathyContactListFlags    flags;
1759
1760         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1761
1762         priv = GET_PRIV (view);
1763
1764         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1765         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1766                 return 0;
1767         }
1768
1769         gtk_tree_model_get (model, &iter,
1770                             EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1771                             -1);
1772
1773         return flags;
1774 }
1775
1776 gchar *
1777 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1778                                               gboolean *is_fake_group)
1779 {
1780         EmpathyContactListViewPriv *priv;
1781         GtkTreeSelection          *selection;
1782         GtkTreeIter                iter;
1783         GtkTreeModel              *model;
1784         gboolean                   is_group;
1785         gchar                     *name;
1786         gboolean                   fake;
1787
1788         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1789
1790         priv = GET_PRIV (view);
1791
1792         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1793         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1794                 return NULL;
1795         }
1796
1797         gtk_tree_model_get (model, &iter,
1798                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1799                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1800                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1801                             -1);
1802
1803         if (!is_group) {
1804                 g_free (name);
1805                 return NULL;
1806         }
1807
1808         if (is_fake_group != NULL)
1809                 *is_fake_group = fake;
1810
1811         return name;
1812 }
1813
1814 static gboolean
1815 contact_list_view_remove_dialog_show (GtkWindow   *parent,
1816                                       const gchar *message,
1817                                       const gchar *secondary_text)
1818 {
1819         GtkWidget *dialog;
1820         gboolean res;
1821
1822         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1823                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1824                                          "%s", message);
1825         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1826                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1827                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1828                                 NULL);
1829         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1830                                                   "%s", secondary_text);
1831
1832         gtk_widget_show (dialog);
1833
1834         res = gtk_dialog_run (GTK_DIALOG (dialog));
1835         gtk_widget_destroy (dialog);
1836
1837         return (res == GTK_RESPONSE_YES);
1838 }
1839
1840 static void
1841 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1842                                             EmpathyContactListView *view)
1843 {
1844         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1845         gchar                      *group;
1846
1847         group = empathy_contact_list_view_get_selected_group (view, NULL);
1848         if (group) {
1849                 gchar     *text;
1850                 GtkWindow *parent;
1851
1852                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1853                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1854                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1855                         EmpathyContactList *list;
1856
1857                         list = empathy_contact_list_store_get_list_iface (priv->store);
1858                         empathy_contact_list_remove_group (list, group);
1859                 }
1860
1861                 g_free (text);
1862         }
1863
1864         g_free (group);
1865 }
1866
1867 GtkWidget *
1868 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1869 {
1870         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1871         gchar                      *group;
1872         GtkWidget                  *menu;
1873         GtkWidget                  *item;
1874         GtkWidget                  *image;
1875         gboolean                   is_fake_group;
1876
1877         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1878
1879         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1880                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1881                 return NULL;
1882         }
1883
1884         group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
1885         if (!group || is_fake_group) {
1886                 /* We can't alter fake groups */
1887                 return NULL;
1888         }
1889
1890         menu = gtk_menu_new ();
1891
1892         /* FIXME: Not implemented yet
1893         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1894                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1895                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1896                 gtk_widget_show (item);
1897                 g_signal_connect (item, "activate",
1898                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1899                                   view);
1900         }*/
1901
1902         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1903                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1904                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1905                                                       GTK_ICON_SIZE_MENU);
1906                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1907                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1908                 gtk_widget_show (item);
1909                 g_signal_connect (item, "activate",
1910                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1911                                   view);
1912         }
1913
1914         g_free (group);
1915
1916         return menu;
1917 }
1918
1919 static void
1920 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1921                                       EmpathyContactListView *view)
1922 {
1923         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1924         EmpathyContact             *contact;
1925
1926         contact = empathy_contact_list_view_dup_selected (view);
1927
1928         if (contact) {
1929                 gchar     *text;
1930                 GtkWindow *parent;
1931
1932                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1933                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1934                                         empathy_contact_get_name (contact));
1935                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1936                         EmpathyContactList *list;
1937
1938                         list = empathy_contact_list_store_get_list_iface (priv->store);
1939                         empathy_contact_list_remove (list, contact, "");
1940                 }
1941
1942                 g_free (text);
1943                 g_object_unref (contact);
1944         }
1945 }
1946
1947 GtkWidget *
1948 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1949 {
1950         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1951         EmpathyContact             *contact;
1952         GtkWidget                  *menu;
1953         GtkWidget                  *item;
1954         GtkWidget                  *image;
1955         EmpathyContactListFlags     flags;
1956
1957         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1958
1959         contact = empathy_contact_list_view_dup_selected (view);
1960         if (!contact) {
1961                 return NULL;
1962         }
1963         flags = empathy_contact_list_view_get_flags (view);
1964
1965         menu = empathy_contact_menu_new (contact, priv->contact_features);
1966
1967         /* Remove contact */
1968         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1969             flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1970                 /* create the menu if required, or just add a separator */
1971                 if (!menu) {
1972                         menu = gtk_menu_new ();
1973                 } else {
1974                         item = gtk_separator_menu_item_new ();
1975                         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1976                         gtk_widget_show (item);
1977                 }
1978
1979                 /* Remove */
1980                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1981                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1982                                                       GTK_ICON_SIZE_MENU);
1983                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1984                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1985                 gtk_widget_show (item);
1986                 g_signal_connect (item, "activate",
1987                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1988                                   view);
1989         }
1990
1991         g_object_unref (contact);
1992
1993         return menu;
1994 }
1995
1996 void
1997 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
1998                                            EmpathyLiveSearch      *search)
1999 {
2000         EmpathyContactListViewPriv *priv = GET_PRIV (view);
2001
2002         /* remove old handlers if old search was not null */
2003         if (priv->search_widget != NULL) {
2004                 g_signal_handlers_disconnect_by_func (view,
2005                         contact_list_view_start_search_cb,
2006                         NULL);
2007
2008                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2009                         contact_list_view_search_text_notify_cb,
2010                         view);
2011                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2012                         contact_list_view_search_hide_cb,
2013                         view);
2014                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2015                         contact_list_view_search_show_cb,
2016                         view);
2017                 g_object_unref (priv->search_widget);
2018                 priv->search_widget = NULL;
2019         }
2020
2021         /* connect handlers if new search is not null */
2022         if (search != NULL) {
2023                 priv->search_widget = g_object_ref (search);
2024
2025                 g_signal_connect (view, "start-interactive-search",
2026                                   G_CALLBACK (contact_list_view_start_search_cb),
2027                                   NULL);
2028
2029                 g_signal_connect (priv->search_widget, "notify::text",
2030                         G_CALLBACK (contact_list_view_search_text_notify_cb),
2031                         view);
2032                 g_signal_connect (priv->search_widget, "hide",
2033                         G_CALLBACK (contact_list_view_search_hide_cb),
2034                         view);
2035                 g_signal_connect (priv->search_widget, "show",
2036                         G_CALLBACK (contact_list_view_search_show_cb),
2037                         view);
2038         }
2039 }