]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Updated Russian translation
[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_alias (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         if (info == DND_DRAG_TYPE_CONTACT_ID) {
767                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
768                                         (guchar *) str, strlen (str) + 1);
769         }
770
771         g_free (str);
772 }
773
774 static void
775 contact_list_view_drag_end (GtkWidget      *widget,
776                             GdkDragContext *context)
777 {
778         EmpathyContactListViewPriv *priv;
779
780         priv = GET_PRIV (widget);
781
782         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
783                                                                             context);
784
785         if (priv->drag_row) {
786                 gtk_tree_row_reference_free (priv->drag_row);
787                 priv->drag_row = NULL;
788         }
789 }
790
791 static gboolean
792 contact_list_view_drag_drop (GtkWidget      *widget,
793                              GdkDragContext *drag_context,
794                              gint            x,
795                              gint            y,
796                              guint           time_)
797 {
798         return FALSE;
799 }
800
801 typedef struct {
802         EmpathyContactListView *view;
803         guint                   button;
804         guint32                 time;
805 } MenuPopupData;
806
807 static gboolean
808 contact_list_view_popup_menu_idle_cb (gpointer user_data)
809 {
810         MenuPopupData *data = user_data;
811         GtkWidget     *menu;
812
813         menu = empathy_contact_list_view_get_contact_menu (data->view);
814         if (!menu) {
815                 menu = empathy_contact_list_view_get_group_menu (data->view);
816         }
817
818         if (menu) {
819                 g_signal_connect (menu, "deactivate",
820                                   G_CALLBACK (gtk_menu_detach), NULL);
821                 gtk_menu_attach_to_widget (GTK_MENU (menu),
822                                            GTK_WIDGET (data->view), NULL);
823                 gtk_widget_show (menu);
824                 gtk_menu_popup (GTK_MENU (menu),
825                                 NULL, NULL, NULL, NULL,
826                                 data->button, data->time);
827                 g_object_ref_sink (menu);
828                 g_object_unref (menu);
829         }
830
831         g_slice_free (MenuPopupData, data);
832
833         return FALSE;
834 }
835
836 static gboolean
837 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
838                                          GdkEventButton         *event,
839                                          gpointer                user_data)
840 {
841         if (event->button == 3) {
842                 MenuPopupData *data;
843
844                 data = g_slice_new (MenuPopupData);
845                 data->view = view;
846                 data->button = event->button;
847                 data->time = event->time;
848                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
849         }
850
851         return FALSE;
852 }
853
854 static gboolean
855 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
856                                       GdkEventKey            *event,
857                                       gpointer                user_data)
858 {
859         if (event->keyval == GDK_Menu) {
860                 MenuPopupData *data;
861
862                 data = g_slice_new (MenuPopupData);
863                 data->view = view;
864                 data->button = 0;
865                 data->time = event->time;
866                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
867         }
868
869         return FALSE;
870 }
871
872 static void
873 contact_list_view_row_activated (GtkTreeView       *view,
874                                  GtkTreePath       *path,
875                                  GtkTreeViewColumn *column)
876 {
877         EmpathyContactListViewPriv *priv = GET_PRIV (view);
878         EmpathyContact             *contact;
879         GtkTreeModel               *model;
880         GtkTreeIter                 iter;
881
882         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
883                 return;
884         }
885
886         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
887         gtk_tree_model_get_iter (model, &iter, path);
888         gtk_tree_model_get (model, &iter,
889                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
890                             -1);
891
892         if (contact) {
893                 DEBUG ("Starting a chat");
894                 empathy_dispatcher_chat_with_contact (contact,
895                         gtk_get_current_event_time ());
896                 g_object_unref (contact);
897         }
898 }
899
900 static void
901 contact_list_view_call_activated_cb (
902     EmpathyCellRendererActivatable *cell,
903     const gchar                    *path_string,
904     EmpathyContactListView         *view)
905 {
906         GtkWidget *menu;
907         GtkTreeModel *model;
908         GtkTreeIter iter;
909         EmpathyContact *contact;
910         GdkEventButton *event;
911         GtkMenuShell *shell;
912         GtkWidget *item;
913
914         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
915         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
916                 return;
917
918         gtk_tree_model_get (model, &iter,
919                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
920                             -1);
921         if (contact == NULL)
922                 return;
923
924         event = (GdkEventButton *) gtk_get_current_event ();
925
926         menu = gtk_menu_new ();
927         shell = GTK_MENU_SHELL (menu);
928
929         /* audio */
930         item = empathy_contact_audio_call_menu_item_new (contact);
931         gtk_menu_shell_append (shell, item);
932         gtk_widget_show (item);
933
934         /* video */
935         item = empathy_contact_video_call_menu_item_new (contact);
936         gtk_menu_shell_append (shell, item);
937         gtk_widget_show (item);
938
939         g_signal_connect (menu, "deactivate",
940                           G_CALLBACK (gtk_menu_detach), NULL);
941         gtk_menu_attach_to_widget (GTK_MENU (menu),
942                                    GTK_WIDGET (view), NULL);
943         gtk_widget_show (menu);
944         gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
945                         event->button, event->time);
946         g_object_ref_sink (menu);
947         g_object_unref (menu);
948
949         g_object_unref (contact);
950 }
951
952 static void
953 contact_list_view_cell_set_background (EmpathyContactListView *view,
954                                        GtkCellRenderer        *cell,
955                                        gboolean                is_group,
956                                        gboolean                is_active)
957 {
958         GdkColor  color;
959         GtkStyle *style;
960
961         style = gtk_widget_get_style (GTK_WIDGET (view));
962
963         if (!is_group && is_active) {
964                 color = style->bg[GTK_STATE_SELECTED];
965
966                 /* Here we take the current theme colour and add it to
967                  * the colour for white and average the two. This
968                  * gives a colour which is inline with the theme but
969                  * slightly whiter.
970                  */
971                 color.red = (color.red + (style->white).red) / 2;
972                 color.green = (color.green + (style->white).green) / 2;
973                 color.blue = (color.blue + (style->white).blue) / 2;
974
975                 g_object_set (cell,
976                               "cell-background-gdk", &color,
977                               NULL);
978         } else {
979                 g_object_set (cell,
980                               "cell-background-gdk", NULL,
981                               NULL);
982         }
983 }
984
985 static void
986 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn      *tree_column,
987                                          GtkCellRenderer        *cell,
988                                          GtkTreeModel           *model,
989                                          GtkTreeIter            *iter,
990                                          EmpathyContactListView *view)
991 {
992         GdkPixbuf *pixbuf;
993         gboolean   is_group;
994         gboolean   is_active;
995
996         gtk_tree_model_get (model, iter,
997                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
998                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
999                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &pixbuf,
1000                             -1);
1001
1002         g_object_set (cell,
1003                       "visible", !is_group,
1004                       "pixbuf", pixbuf,
1005                       NULL);
1006
1007         if (pixbuf != NULL) {
1008                 g_object_unref (pixbuf);
1009         }
1010
1011         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1012 }
1013
1014 static void
1015 contact_list_view_group_icon_cell_data_func (GtkTreeViewColumn     *tree_column,
1016                                              GtkCellRenderer       *cell,
1017                                              GtkTreeModel          *model,
1018                                              GtkTreeIter           *iter,
1019                                              EmpathyContactListView *view)
1020 {
1021         GdkPixbuf *pixbuf = NULL;
1022         gboolean is_group;
1023         gchar *name;
1024
1025         gtk_tree_model_get (model, iter,
1026                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1027                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1028                             -1);
1029
1030         if (!is_group)
1031                 goto out;
1032
1033         if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_FAVORITE)) {
1034                 pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1035                         GTK_ICON_SIZE_MENU);
1036         }
1037         else if (!tp_strdiff (name, EMPATHY_CONTACT_LIST_STORE_PEOPLE_NEARBY)) {
1038                 pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1039                         GTK_ICON_SIZE_MENU);
1040         }
1041
1042 out:
1043         g_object_set (cell,
1044                       "visible", pixbuf != NULL,
1045                       "pixbuf", pixbuf,
1046                       NULL);
1047
1048         if (pixbuf != NULL)
1049                 g_object_unref (pixbuf);
1050
1051         g_free (name);
1052 }
1053
1054 static void
1055 contact_list_view_audio_call_cell_data_func (
1056                                        GtkTreeViewColumn      *tree_column,
1057                                        GtkCellRenderer        *cell,
1058                                        GtkTreeModel           *model,
1059                                        GtkTreeIter            *iter,
1060                                        EmpathyContactListView *view)
1061 {
1062         gboolean is_group;
1063         gboolean is_active;
1064         gboolean can_audio, can_video;
1065
1066         gtk_tree_model_get (model, iter,
1067                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1068                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1069                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1070                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_video,
1071                             -1);
1072
1073         g_object_set (cell,
1074                       "visible", !is_group && (can_audio || can_video),
1075                       "icon-name", can_video? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1076                       NULL);
1077
1078         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1079 }
1080
1081 static void
1082 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn      *tree_column,
1083                                          GtkCellRenderer        *cell,
1084                                          GtkTreeModel           *model,
1085                                          GtkTreeIter            *iter,
1086                                          EmpathyContactListView *view)
1087 {
1088         GdkPixbuf *pixbuf;
1089         gboolean   show_avatar;
1090         gboolean   is_group;
1091         gboolean   is_active;
1092
1093         gtk_tree_model_get (model, iter,
1094                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1095                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1096                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1097                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1098                             -1);
1099
1100         g_object_set (cell,
1101                       "visible", !is_group && show_avatar,
1102                       "pixbuf", pixbuf,
1103                       NULL);
1104
1105         if (pixbuf) {
1106                 g_object_unref (pixbuf);
1107         }
1108
1109         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1110 }
1111
1112 static void
1113 contact_list_view_text_cell_data_func (GtkTreeViewColumn      *tree_column,
1114                                        GtkCellRenderer        *cell,
1115                                        GtkTreeModel           *model,
1116                                        GtkTreeIter            *iter,
1117                                        EmpathyContactListView *view)
1118 {
1119         gboolean is_group;
1120         gboolean is_active;
1121
1122         gtk_tree_model_get (model, iter,
1123                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1124                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1125                             -1);
1126
1127         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1128 }
1129
1130 static void
1131 contact_list_view_expander_cell_data_func (GtkTreeViewColumn      *column,
1132                                            GtkCellRenderer        *cell,
1133                                            GtkTreeModel           *model,
1134                                            GtkTreeIter            *iter,
1135                                            EmpathyContactListView *view)
1136 {
1137         gboolean is_group;
1138         gboolean is_active;
1139
1140         gtk_tree_model_get (model, iter,
1141                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1142                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
1143                             -1);
1144
1145         if (gtk_tree_model_iter_has_child (model, iter)) {
1146                 GtkTreePath *path;
1147                 gboolean     row_expanded;
1148
1149                 path = gtk_tree_model_get_path (model, iter);
1150                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
1151                 gtk_tree_path_free (path);
1152
1153                 g_object_set (cell,
1154                               "visible", TRUE,
1155                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1156                               NULL);
1157         } else {
1158                 g_object_set (cell, "visible", FALSE, NULL);
1159         }
1160
1161         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1162 }
1163
1164 static void
1165 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1166                                              GtkTreeIter            *iter,
1167                                              GtkTreePath            *path,
1168                                              gpointer                user_data)
1169 {
1170         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1171         GtkTreeModel               *model;
1172         gchar                      *name;
1173         gboolean                    expanded;
1174
1175         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1176                 return;
1177         }
1178
1179         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1180
1181         gtk_tree_model_get (model, iter,
1182                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1183                             -1);
1184
1185         expanded = GPOINTER_TO_INT (user_data);
1186         empathy_contact_group_set_expanded (name, expanded);
1187
1188         g_free (name);
1189 }
1190
1191 static gboolean
1192 contact_list_view_start_search_cb (EmpathyContactListView *view,
1193                                    gpointer                data)
1194 {
1195         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1196
1197         if (priv->search_widget == NULL)
1198                 return FALSE;
1199
1200         if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1201                 gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1202         else
1203                 gtk_widget_show (GTK_WIDGET (priv->search_widget));
1204
1205         return TRUE;
1206 }
1207
1208 static void
1209 contact_list_view_search_text_notify_cb (EmpathyLiveSearch      *search,
1210                                          GParamSpec             *pspec,
1211                                          EmpathyContactListView *view)
1212 {
1213         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1214         GtkTreePath *path;
1215         GtkTreeViewColumn *focus_column;
1216         GtkTreeModel *model;
1217         GtkTreeIter iter;
1218         gboolean set_cursor = FALSE;
1219
1220         gtk_tree_model_filter_refilter (priv->filter);
1221
1222         /* Set cursor on the first contact. If it is already set on a group,
1223          * set it on its first child contact. Note that first child of a group
1224          * is its separator, that's why we actually set to the 2nd
1225          */
1226
1227         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1228         gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1229
1230         if (path == NULL) {
1231                 path = gtk_tree_path_new_from_string ("0:1");
1232                 set_cursor = TRUE;
1233         } else if (gtk_tree_path_get_depth (path) < 2) {
1234                 gboolean is_group;
1235
1236                 gtk_tree_model_get_iter (model, &iter, path);
1237                 gtk_tree_model_get (model, &iter,
1238                         EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1239                         -1);
1240
1241                 if (is_group) {
1242                         gtk_tree_path_down (path);
1243                         gtk_tree_path_next (path);
1244                         set_cursor = TRUE;
1245                 }
1246         }
1247
1248         if (set_cursor) {
1249                 /* FIXME: Workaround for GTK bug #621651, we have to make sure
1250                  * the path is valid. */
1251                 if (gtk_tree_model_get_iter (model, &iter, path)) {
1252                         gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path,
1253                                 focus_column, FALSE);
1254                 }
1255         }
1256
1257         gtk_tree_path_free (path);
1258 }
1259
1260 static void
1261 contact_list_view_search_activate_cb (GtkWidget *search,
1262                                       EmpathyContactListView *view)
1263 {
1264         GtkTreePath *path;
1265         GtkTreeViewColumn *focus_column;
1266
1267         gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1268         if (path != NULL) {
1269                 gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path,
1270                         focus_column);
1271                 gtk_tree_path_free (path);
1272
1273                 gtk_widget_hide (search);
1274         }
1275 }
1276
1277 static gboolean
1278 contact_list_view_search_key_navigation_cb (GtkWidget *search,
1279                                             GdkEvent *event,
1280                                             EmpathyContactListView *view)
1281 {
1282         GdkEventKey *eventkey = ((GdkEventKey *) event);
1283         gboolean ret = FALSE;
1284
1285         if (eventkey->keyval == GDK_Up || eventkey->keyval == GDK_Down) {
1286                 GdkEvent *new_event;
1287
1288                 new_event = gdk_event_copy (event);
1289                 gtk_widget_grab_focus (GTK_WIDGET (view));
1290                 ret = gtk_widget_event (GTK_WIDGET (view), new_event);
1291                 gtk_widget_grab_focus (search);
1292
1293                 gdk_event_free (new_event);
1294         }
1295
1296         return ret;
1297 }
1298
1299 static void
1300 contact_list_view_search_hide_cb (EmpathyLiveSearch      *search,
1301                                   EmpathyContactListView *view)
1302 {
1303         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1304         GtkTreeModel               *model;
1305         GtkTreeIter                 iter;
1306         gboolean                    valid = FALSE;
1307
1308         /* block expand or collapse handlers, they would write the
1309          * expand or collapsed setting to file otherwise */
1310         g_signal_handlers_block_by_func (view,
1311                 contact_list_view_row_expand_or_collapse_cb,
1312                 GINT_TO_POINTER (TRUE));
1313         g_signal_handlers_block_by_func (view,
1314                 contact_list_view_row_expand_or_collapse_cb,
1315                 GINT_TO_POINTER (FALSE));
1316
1317         /* restore which groups are expanded and which are not */
1318         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1319         for (valid = gtk_tree_model_get_iter_first (model, &iter);
1320              valid; valid = gtk_tree_model_iter_next (model, &iter)) {
1321                 gboolean      is_group;
1322                 gchar        *name = NULL;
1323                 GtkTreePath  *path;
1324
1325                 gtk_tree_model_get (model, &iter,
1326                         EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1327                         EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1328                         -1);
1329
1330                 if (!is_group) {
1331                         g_free (name);
1332                         continue;
1333                 }
1334
1335                 path = gtk_tree_model_get_path (model, &iter);
1336                 if ((priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1337                     empathy_contact_group_get_expanded (name)) {
1338                         gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path,
1339                                 TRUE);
1340                 } else {
1341                         gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1342                 }
1343
1344                 gtk_tree_path_free (path);
1345                 g_free (name);
1346         }
1347
1348         /* unblock expand or collapse handlers */
1349         g_signal_handlers_unblock_by_func (view,
1350                 contact_list_view_row_expand_or_collapse_cb,
1351                 GINT_TO_POINTER (TRUE));
1352         g_signal_handlers_unblock_by_func (view,
1353                 contact_list_view_row_expand_or_collapse_cb,
1354                 GINT_TO_POINTER (FALSE));
1355 }
1356
1357 static void
1358 contact_list_view_search_show_cb (EmpathyLiveSearch      *search,
1359                                   EmpathyContactListView *view)
1360 {
1361         /* block expand or collapse handlers during expand all, they would
1362          * write the expand or collapsed setting to file otherwise */
1363         g_signal_handlers_block_by_func (view,
1364                 contact_list_view_row_expand_or_collapse_cb,
1365                 GINT_TO_POINTER (TRUE));
1366
1367         gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1368
1369         g_signal_handlers_unblock_by_func (view,
1370                 contact_list_view_row_expand_or_collapse_cb,
1371                 GINT_TO_POINTER (TRUE));
1372 }
1373
1374 typedef struct {
1375         EmpathyContactListView *view;
1376         GtkTreeRowReference *row_ref;
1377         gboolean expand;
1378 } ExpandData;
1379
1380 static gboolean
1381 contact_list_view_expand_idle_cb (gpointer user_data)
1382 {
1383         ExpandData *data = user_data;
1384         GtkTreePath *path;
1385
1386         path = gtk_tree_row_reference_get_path (data->row_ref);
1387         if (path == NULL)
1388                 goto done;
1389
1390         g_signal_handlers_block_by_func (data->view,
1391                 contact_list_view_row_expand_or_collapse_cb,
1392                 GINT_TO_POINTER (data->expand));
1393
1394         if (data->expand) {
1395                 gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path,
1396                     TRUE);
1397         } else {
1398                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1399         }
1400         gtk_tree_path_free (path);
1401
1402         g_signal_handlers_unblock_by_func (data->view,
1403                 contact_list_view_row_expand_or_collapse_cb,
1404                 GINT_TO_POINTER (data->expand));
1405
1406 done:
1407         g_object_unref (data->view);
1408         gtk_tree_row_reference_free (data->row_ref);
1409         g_slice_free (ExpandData, data);
1410
1411         return FALSE;
1412 }
1413
1414 static void
1415 contact_list_view_row_has_child_toggled_cb (GtkTreeModel           *model,
1416                                             GtkTreePath            *path,
1417                                             GtkTreeIter            *iter,
1418                                             EmpathyContactListView *view)
1419 {
1420         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1421         gboolean  is_group = FALSE;
1422         gchar    *name = NULL;
1423         ExpandData *data;
1424
1425         gtk_tree_model_get (model, iter,
1426                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1427                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1428                             -1);
1429
1430         if (!is_group || EMP_STR_EMPTY (name)) {
1431                 g_free (name);
1432                 return;
1433         }
1434
1435         data = g_slice_new0 (ExpandData);
1436         data->view = g_object_ref (view);
1437         data->row_ref = gtk_tree_row_reference_new (model, path);
1438         data->expand =
1439                 (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) == 0 ||
1440                 (priv->search_widget != NULL && gtk_widget_get_visible (priv->search_widget)) ||
1441                 empathy_contact_group_get_expanded (name);
1442
1443         /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1444          * gtk_tree_model_filter_refilter () */
1445         g_idle_add (contact_list_view_expand_idle_cb, data);
1446
1447         g_free (name);
1448 }
1449
1450 static void
1451 contact_list_view_verify_group_visibility (EmpathyContactListView *view,
1452                                            GtkTreePath            *path)
1453 {
1454         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1455         GtkTreeModel *model;
1456         GtkTreePath *parent_path;
1457         GtkTreeIter parent_iter;
1458
1459         if (gtk_tree_path_get_depth (path) < 2)
1460                 return;
1461
1462         /* A group row is visible if and only if at least one if its child is
1463          * visible. So when a row is inserted/deleted/changed in the base model,
1464          * that could modify the visibility of its parent in the filter model.
1465          */
1466
1467         model = GTK_TREE_MODEL (priv->store);
1468         parent_path = gtk_tree_path_copy (path);
1469         gtk_tree_path_up (parent_path);
1470         if (gtk_tree_model_get_iter (model, &parent_iter, parent_path)) {
1471                 /* This tells the filter to verify the visibility of that row,
1472                  * and show/hide it if necessary */
1473                 gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1474                         parent_path, &parent_iter);
1475         }
1476         gtk_tree_path_free (parent_path);
1477 }
1478
1479 static void
1480 contact_list_view_store_row_changed_cb (GtkTreeModel           *model,
1481                                         GtkTreePath            *path,
1482                                         GtkTreeIter            *iter,
1483                                         EmpathyContactListView *view)
1484 {
1485         contact_list_view_verify_group_visibility (view, path);
1486 }
1487
1488 static void
1489 contact_list_view_store_row_deleted_cb (GtkTreeModel           *model,
1490                                         GtkTreePath            *path,
1491                                         EmpathyContactListView *view)
1492 {
1493         contact_list_view_verify_group_visibility (view, path);
1494 }
1495
1496 static void
1497 contact_list_view_constructed (GObject *object)
1498 {
1499         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1500         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1501         GtkCellRenderer            *cell;
1502         GtkTreeViewColumn          *col;
1503         guint                       i;
1504
1505         priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1506                         GTK_TREE_MODEL (priv->store), NULL));
1507         gtk_tree_model_filter_set_visible_func (priv->filter,
1508                         contact_list_view_filter_visible_func,
1509                         view, NULL);
1510
1511         g_signal_connect (priv->filter, "row-has-child-toggled",
1512                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1513                           view);
1514
1515         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1516                                  GTK_TREE_MODEL (priv->filter));
1517
1518         tp_g_signal_connect_object (priv->store, "row-changed",
1519                 G_CALLBACK (contact_list_view_store_row_changed_cb),
1520                 view, 0);
1521         tp_g_signal_connect_object (priv->store, "row-inserted",
1522                 G_CALLBACK (contact_list_view_store_row_changed_cb),
1523                 view, 0);
1524         tp_g_signal_connect_object (priv->store, "row-deleted",
1525                 G_CALLBACK (contact_list_view_store_row_deleted_cb),
1526                 view, 0);
1527
1528         /* Setup view */
1529         /* Setting reorderable is a hack that gets us row previews as drag icons
1530            for free.  We override all the drag handlers.  It's tricky to get the
1531            position of the drag icon right in drag_begin.  GtkTreeView has special
1532            voodoo for it, so we let it do the voodoo that he do.
1533          */
1534         g_object_set (view,
1535                       "headers-visible", FALSE,
1536                       "reorderable", TRUE,
1537                       "show-expanders", FALSE,
1538                       NULL);
1539
1540         col = gtk_tree_view_column_new ();
1541
1542         /* State */
1543         cell = gtk_cell_renderer_pixbuf_new ();
1544         gtk_tree_view_column_pack_start (col, cell, FALSE);
1545         gtk_tree_view_column_set_cell_data_func (
1546                 col, cell,
1547                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1548                 view, NULL);
1549
1550         g_object_set (cell,
1551                       "xpad", 5,
1552                       "ypad", 1,
1553                       "visible", FALSE,
1554                       NULL);
1555
1556         /* Group icon */
1557         cell = gtk_cell_renderer_pixbuf_new ();
1558         gtk_tree_view_column_pack_start (col, cell, FALSE);
1559         gtk_tree_view_column_set_cell_data_func (
1560                 col, cell,
1561                 (GtkTreeCellDataFunc) contact_list_view_group_icon_cell_data_func,
1562                 view, NULL);
1563
1564         g_object_set (cell,
1565                       "xpad", 0,
1566                       "ypad", 0,
1567                       "visible", FALSE,
1568                       "width", 16,
1569                       "height", 16,
1570                       NULL);
1571
1572         /* Name */
1573         cell = empathy_cell_renderer_text_new ();
1574         gtk_tree_view_column_pack_start (col, cell, TRUE);
1575         gtk_tree_view_column_set_cell_data_func (
1576                 col, cell,
1577                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1578                 view, NULL);
1579
1580         gtk_tree_view_column_add_attribute (col, cell,
1581                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1582         gtk_tree_view_column_add_attribute (col, cell,
1583                                             "text", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1584         gtk_tree_view_column_add_attribute (col, cell,
1585                                             "presence-type", EMPATHY_CONTACT_LIST_STORE_COL_PRESENCE_TYPE);
1586         gtk_tree_view_column_add_attribute (col, cell,
1587                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1588         gtk_tree_view_column_add_attribute (col, cell,
1589                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1590         gtk_tree_view_column_add_attribute (col, cell,
1591                                             "compact", EMPATHY_CONTACT_LIST_STORE_COL_COMPACT);
1592
1593         /* Audio Call Icon */
1594         cell = empathy_cell_renderer_activatable_new ();
1595         gtk_tree_view_column_pack_start (col, cell, FALSE);
1596         gtk_tree_view_column_set_cell_data_func (
1597                 col, cell,
1598                 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1599                 view, NULL);
1600
1601         g_object_set (cell,
1602                       "visible", FALSE,
1603                       NULL);
1604
1605         g_signal_connect (cell, "path-activated",
1606                           G_CALLBACK (contact_list_view_call_activated_cb),
1607                           view);
1608
1609         /* Avatar */
1610         cell = gtk_cell_renderer_pixbuf_new ();
1611         gtk_tree_view_column_pack_start (col, cell, FALSE);
1612         gtk_tree_view_column_set_cell_data_func (
1613                 col, cell,
1614                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1615                 view, NULL);
1616
1617         g_object_set (cell,
1618                       "xpad", 0,
1619                       "ypad", 0,
1620                       "visible", FALSE,
1621                       "width", 32,
1622                       "height", 32,
1623                       NULL);
1624
1625         /* Expander */
1626         cell = empathy_cell_renderer_expander_new ();
1627         gtk_tree_view_column_pack_end (col, cell, FALSE);
1628         gtk_tree_view_column_set_cell_data_func (
1629                 col, cell,
1630                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1631                 view, NULL);
1632
1633         /* Actually add the column now we have added all cell renderers */
1634         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1635
1636         /* Drag & Drop. */
1637         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1638                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1639                                                       FALSE);
1640         }
1641
1642         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1643                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1644                                                         FALSE);
1645         }
1646 }
1647
1648 static void
1649 contact_list_view_set_list_features (EmpathyContactListView         *view,
1650                                      EmpathyContactListFeatureFlags  features)
1651 {
1652         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1653         gboolean                    has_tooltip;
1654
1655         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1656
1657         priv->list_features = features;
1658
1659         /* Update DnD source/dest */
1660         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1661                 gtk_drag_source_set (GTK_WIDGET (view),
1662                                      GDK_BUTTON1_MASK,
1663                                      drag_types_source,
1664                                      G_N_ELEMENTS (drag_types_source),
1665                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1666         } else {
1667                 gtk_drag_source_unset (GTK_WIDGET (view));
1668
1669         }
1670
1671         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1672                 gtk_drag_dest_set (GTK_WIDGET (view),
1673                                    GTK_DEST_DEFAULT_ALL,
1674                                    drag_types_dest,
1675                                    G_N_ELEMENTS (drag_types_dest),
1676                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1677         } else {
1678                 /* FIXME: URI could still be droped depending on FT feature */
1679                 gtk_drag_dest_unset (GTK_WIDGET (view));
1680         }
1681
1682         /* Update has-tooltip */
1683         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1684         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1685 }
1686
1687 static void
1688 contact_list_view_dispose (GObject *object)
1689 {
1690         EmpathyContactListView *view = EMPATHY_CONTACT_LIST_VIEW (object);
1691         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1692
1693         if (priv->store) {
1694                 g_object_unref (priv->store);
1695                 priv->store = NULL;
1696         }
1697         if (priv->filter) {
1698                 g_object_unref (priv->filter);
1699                 priv->filter = NULL;
1700         }
1701         if (priv->tooltip_widget) {
1702                 gtk_widget_destroy (priv->tooltip_widget);
1703                 priv->tooltip_widget = NULL;
1704         }
1705         if (priv->file_targets) {
1706                 gtk_target_list_unref (priv->file_targets);
1707                 priv->file_targets = NULL;
1708         }
1709
1710         empathy_contact_list_view_set_live_search (view, NULL);
1711
1712         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->dispose (object);
1713 }
1714
1715 static void
1716 contact_list_view_get_property (GObject    *object,
1717                                 guint       param_id,
1718                                 GValue     *value,
1719                                 GParamSpec *pspec)
1720 {
1721         EmpathyContactListViewPriv *priv;
1722
1723         priv = GET_PRIV (object);
1724
1725         switch (param_id) {
1726         case PROP_STORE:
1727                 g_value_set_object (value, priv->store);
1728                 break;
1729         case PROP_LIST_FEATURES:
1730                 g_value_set_flags (value, priv->list_features);
1731                 break;
1732         case PROP_CONTACT_FEATURES:
1733                 g_value_set_flags (value, priv->contact_features);
1734                 break;
1735         default:
1736                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1737                 break;
1738         };
1739 }
1740
1741 static void
1742 contact_list_view_set_property (GObject      *object,
1743                                 guint         param_id,
1744                                 const GValue *value,
1745                                 GParamSpec   *pspec)
1746 {
1747         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1748         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1749
1750         switch (param_id) {
1751         case PROP_STORE:
1752                 priv->store = g_value_dup_object (value);
1753                 break;
1754         case PROP_LIST_FEATURES:
1755                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1756                 break;
1757         case PROP_CONTACT_FEATURES:
1758                 priv->contact_features = g_value_get_flags (value);
1759                 break;
1760         default:
1761                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1762                 break;
1763         };
1764 }
1765
1766 static void
1767 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1768 {
1769         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1770         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1771         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1772
1773         object_class->constructed        = contact_list_view_constructed;
1774         object_class->dispose            = contact_list_view_dispose;
1775         object_class->get_property       = contact_list_view_get_property;
1776         object_class->set_property       = contact_list_view_set_property;
1777
1778         widget_class->drag_data_received = contact_list_view_drag_data_received;
1779         widget_class->drag_drop          = contact_list_view_drag_drop;
1780         widget_class->drag_begin         = contact_list_view_drag_begin;
1781         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1782         widget_class->drag_end           = contact_list_view_drag_end;
1783         widget_class->drag_motion        = contact_list_view_drag_motion;
1784
1785         /* We use the class method to let user of this widget to connect to
1786          * the signal and stop emission of the signal so the default handler
1787          * won't be called. */
1788         tree_view_class->row_activated = contact_list_view_row_activated;
1789
1790         signals[DRAG_CONTACT_RECEIVED] =
1791                 g_signal_new ("drag-contact-received",
1792                               G_OBJECT_CLASS_TYPE (klass),
1793                               G_SIGNAL_RUN_LAST,
1794                               0,
1795                               NULL, NULL,
1796                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1797                               G_TYPE_NONE,
1798                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1799
1800         g_object_class_install_property (object_class,
1801                                          PROP_STORE,
1802                                          g_param_spec_object ("store",
1803                                                              "The store of the view",
1804                                                              "The store of the view",
1805                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1806                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1807         g_object_class_install_property (object_class,
1808                                          PROP_LIST_FEATURES,
1809                                          g_param_spec_flags ("list-features",
1810                                                              "Features of the view",
1811                                                              "Flags for all enabled features",
1812                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1813                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1814                                                               G_PARAM_READWRITE));
1815         g_object_class_install_property (object_class,
1816                                          PROP_CONTACT_FEATURES,
1817                                          g_param_spec_flags ("contact-features",
1818                                                              "Features of the contact menu",
1819                                                              "Flags for all enabled features for the menu",
1820                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1821                                                               EMPATHY_CONTACT_FEATURE_NONE,
1822                                                               G_PARAM_READWRITE));
1823
1824         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1825 }
1826
1827 static void
1828 empathy_contact_list_view_init (EmpathyContactListView *view)
1829 {
1830         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1831                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1832
1833         view->priv = priv;
1834
1835         /* Get saved group states. */
1836         empathy_contact_groups_get_all ();
1837
1838         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1839                                               empathy_contact_list_store_row_separator_func,
1840                                               NULL, NULL);
1841
1842         /* Set up drag target lists. */
1843         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1844                                                   G_N_ELEMENTS (drag_types_dest_file));
1845
1846         /* Connect to tree view signals rather than override. */
1847         g_signal_connect (view, "button-press-event",
1848                           G_CALLBACK (contact_list_view_button_press_event_cb),
1849                           NULL);
1850         g_signal_connect (view, "key-press-event",
1851                           G_CALLBACK (contact_list_view_key_press_event_cb),
1852                           NULL);
1853         g_signal_connect (view, "row-expanded",
1854                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1855                           GINT_TO_POINTER (TRUE));
1856         g_signal_connect (view, "row-collapsed",
1857                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1858                           GINT_TO_POINTER (FALSE));
1859         g_signal_connect (view, "query-tooltip",
1860                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1861                           NULL);
1862 }
1863
1864 EmpathyContactListView *
1865 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1866                                EmpathyContactListFeatureFlags  list_features,
1867                                EmpathyContactFeatureFlags      contact_features)
1868 {
1869         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1870
1871         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1872                              "store", store,
1873                              "contact-features", contact_features,
1874                              "list-features", list_features,
1875                              NULL);
1876 }
1877
1878 EmpathyContact *
1879 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1880 {
1881         EmpathyContactListViewPriv *priv;
1882         GtkTreeSelection          *selection;
1883         GtkTreeIter                iter;
1884         GtkTreeModel              *model;
1885         EmpathyContact             *contact;
1886
1887         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1888
1889         priv = GET_PRIV (view);
1890
1891         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1892         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1893                 return NULL;
1894         }
1895
1896         gtk_tree_model_get (model, &iter,
1897                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1898                             -1);
1899
1900         return contact;
1901 }
1902
1903 EmpathyContactListFlags
1904 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1905 {
1906         EmpathyContactListViewPriv *priv;
1907         GtkTreeSelection          *selection;
1908         GtkTreeIter                iter;
1909         GtkTreeModel              *model;
1910         EmpathyContactListFlags    flags;
1911
1912         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1913
1914         priv = GET_PRIV (view);
1915
1916         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1917         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1918                 return 0;
1919         }
1920
1921         gtk_tree_model_get (model, &iter,
1922                             EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1923                             -1);
1924
1925         return flags;
1926 }
1927
1928 gchar *
1929 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view,
1930                                               gboolean *is_fake_group)
1931 {
1932         EmpathyContactListViewPriv *priv;
1933         GtkTreeSelection          *selection;
1934         GtkTreeIter                iter;
1935         GtkTreeModel              *model;
1936         gboolean                   is_group;
1937         gchar                     *name;
1938         gboolean                   fake;
1939
1940         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1941
1942         priv = GET_PRIV (view);
1943
1944         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1945         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1946                 return NULL;
1947         }
1948
1949         gtk_tree_model_get (model, &iter,
1950                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1951                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1952                             EMPATHY_CONTACT_LIST_STORE_COL_IS_FAKE_GROUP, &fake,
1953                             -1);
1954
1955         if (!is_group) {
1956                 g_free (name);
1957                 return NULL;
1958         }
1959
1960         if (is_fake_group != NULL)
1961                 *is_fake_group = fake;
1962
1963         return name;
1964 }
1965
1966 static gboolean
1967 contact_list_view_remove_dialog_show (GtkWindow   *parent,
1968                                       const gchar *message,
1969                                       const gchar *secondary_text)
1970 {
1971         GtkWidget *dialog;
1972         gboolean res;
1973
1974         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1975                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1976                                          "%s", message);
1977         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1978                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1979                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1980                                 NULL);
1981         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1982                                                   "%s", secondary_text);
1983
1984         gtk_widget_show (dialog);
1985
1986         res = gtk_dialog_run (GTK_DIALOG (dialog));
1987         gtk_widget_destroy (dialog);
1988
1989         return (res == GTK_RESPONSE_YES);
1990 }
1991
1992 static void
1993 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1994                                             EmpathyContactListView *view)
1995 {
1996         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1997         gchar                      *group;
1998
1999         group = empathy_contact_list_view_get_selected_group (view, NULL);
2000         if (group) {
2001                 gchar     *text;
2002                 GtkWindow *parent;
2003
2004                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
2005                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2006                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
2007                         EmpathyContactList *list;
2008
2009                         list = empathy_contact_list_store_get_list_iface (priv->store);
2010                         empathy_contact_list_remove_group (list, group);
2011                 }
2012
2013                 g_free (text);
2014         }
2015
2016         g_free (group);
2017 }
2018
2019 GtkWidget *
2020 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
2021 {
2022         EmpathyContactListViewPriv *priv = GET_PRIV (view);
2023         gchar                      *group;
2024         GtkWidget                  *menu;
2025         GtkWidget                  *item;
2026         GtkWidget                  *image;
2027         gboolean                   is_fake_group;
2028
2029         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2030
2031         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
2032                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
2033                 return NULL;
2034         }
2035
2036         group = empathy_contact_list_view_get_selected_group (view, &is_fake_group);
2037         if (!group || is_fake_group) {
2038                 /* We can't alter fake groups */
2039                 return NULL;
2040         }
2041
2042         menu = gtk_menu_new ();
2043
2044         /* FIXME: Not implemented yet
2045         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
2046                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
2047                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2048                 gtk_widget_show (item);
2049                 g_signal_connect (item, "activate",
2050                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
2051                                   view);
2052         }*/
2053
2054         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
2055                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2056                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2057                                                       GTK_ICON_SIZE_MENU);
2058                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2059                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2060                 gtk_widget_show (item);
2061                 g_signal_connect (item, "activate",
2062                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
2063                                   view);
2064         }
2065
2066         g_free (group);
2067
2068         return menu;
2069 }
2070
2071 static void
2072 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
2073                                       EmpathyContactListView *view)
2074 {
2075         EmpathyContactListViewPriv *priv = GET_PRIV (view);
2076         EmpathyContact             *contact;
2077
2078         contact = empathy_contact_list_view_dup_selected (view);
2079
2080         if (contact) {
2081                 gchar     *text;
2082                 GtkWindow *parent;
2083
2084                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2085                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
2086                                         empathy_contact_get_alias (contact));
2087                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
2088                         EmpathyContactList *list;
2089
2090                         list = empathy_contact_list_store_get_list_iface (priv->store);
2091                         empathy_contact_list_remove (list, contact, "");
2092                 }
2093
2094                 g_free (text);
2095                 g_object_unref (contact);
2096         }
2097 }
2098
2099 GtkWidget *
2100 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
2101 {
2102         EmpathyContactListViewPriv *priv = GET_PRIV (view);
2103         EmpathyContact             *contact;
2104         GtkWidget                  *menu;
2105         GtkWidget                  *item;
2106         GtkWidget                  *image;
2107         EmpathyContactListFlags     flags;
2108
2109         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
2110
2111         contact = empathy_contact_list_view_dup_selected (view);
2112         if (!contact) {
2113                 return NULL;
2114         }
2115         flags = empathy_contact_list_view_get_flags (view);
2116
2117         menu = empathy_contact_menu_new (contact, priv->contact_features);
2118
2119         /* Remove contact */
2120         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
2121             flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
2122                 /* create the menu if required, or just add a separator */
2123                 if (!menu) {
2124                         menu = gtk_menu_new ();
2125                 } else {
2126                         item = gtk_separator_menu_item_new ();
2127                         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2128                         gtk_widget_show (item);
2129                 }
2130
2131                 /* Remove */
2132                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2133                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2134                                                       GTK_ICON_SIZE_MENU);
2135                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2136                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2137                 gtk_widget_show (item);
2138                 g_signal_connect (item, "activate",
2139                                   G_CALLBACK (contact_list_view_remove_activate_cb),
2140                                   view);
2141         }
2142
2143         g_object_unref (contact);
2144
2145         return menu;
2146 }
2147
2148 void
2149 empathy_contact_list_view_set_live_search (EmpathyContactListView *view,
2150                                            EmpathyLiveSearch      *search)
2151 {
2152         EmpathyContactListViewPriv *priv = GET_PRIV (view);
2153
2154         /* remove old handlers if old search was not null */
2155         if (priv->search_widget != NULL) {
2156                 g_signal_handlers_disconnect_by_func (view,
2157                         contact_list_view_start_search_cb,
2158                         NULL);
2159
2160                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2161                         contact_list_view_search_text_notify_cb,
2162                         view);
2163                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2164                         contact_list_view_search_activate_cb,
2165                         view);
2166                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2167                         contact_list_view_search_key_navigation_cb,
2168                         view);
2169                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2170                         contact_list_view_search_hide_cb,
2171                         view);
2172                 g_signal_handlers_disconnect_by_func (priv->search_widget,
2173                         contact_list_view_search_show_cb,
2174                         view);
2175                 g_object_unref (priv->search_widget);
2176                 priv->search_widget = NULL;
2177         }
2178
2179         /* connect handlers if new search is not null */
2180         if (search != NULL) {
2181                 priv->search_widget = g_object_ref (search);
2182
2183                 g_signal_connect (view, "start-interactive-search",
2184                                   G_CALLBACK (contact_list_view_start_search_cb),
2185                                   NULL);
2186
2187                 g_signal_connect (priv->search_widget, "notify::text",
2188                         G_CALLBACK (contact_list_view_search_text_notify_cb),
2189                         view);
2190                 g_signal_connect (priv->search_widget, "activate",
2191                         G_CALLBACK (contact_list_view_search_activate_cb),
2192                         view);
2193                 g_signal_connect (priv->search_widget, "key-navigation",
2194                         G_CALLBACK (contact_list_view_search_key_navigation_cb),
2195                         view);
2196                 g_signal_connect (priv->search_widget, "hide",
2197                         G_CALLBACK (contact_list_view_search_hide_cb),
2198                         view);
2199                 g_signal_connect (priv->search_widget, "show",
2200                         G_CALLBACK (contact_list_view_search_show_cb),
2201                         view);
2202         }
2203 }