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