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