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