]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
289595df69fa500d2993356f57c49bb60440d646
[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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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/util.h>
35 #include <libmissioncontrol/mc-account.h>
36
37 #include <libempathy/empathy-account-manager.h>
38 #include <libempathy/empathy-call-factory.h>
39 #include <libempathy/empathy-tp-contact-factory.h>
40 #include <libempathy/empathy-contact-list.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
44
45 #include "empathy-contact-list-view.h"
46 #include "empathy-contact-list-store.h"
47 #include "empathy-images.h"
48 #include "empathy-cell-renderer-expander.h"
49 #include "empathy-cell-renderer-text.h"
50 #include "empathy-cell-renderer-activatable.h"
51 #include "empathy-ui-utils.h"
52 #include "empathy-gtk-enum-types.h"
53 #include "empathy-gtk-marshal.h"
54
55 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
56 #include <libempathy/empathy-debug.h>
57
58 /* Active users are those which have recently changed state
59  * (e.g. online, offline or from normal to a busy state).
60  */
61
62 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
63 typedef struct {
64         EmpathyContactListStore        *store;
65         GtkTreeRowReference            *drag_row;
66         EmpathyContactListFeatureFlags  list_features;
67         EmpathyContactFeatureFlags      contact_features;
68         GtkWidget                      *tooltip_widget;
69 } EmpathyContactListViewPriv;
70
71 typedef struct {
72         EmpathyContactListView *view;
73         GtkTreePath           *path;
74         guint                  timeout_id;
75 } DragMotionData;
76
77 typedef struct {
78         EmpathyContactListView *view;
79         EmpathyContact         *contact;
80         gboolean               remove;
81 } ShowActiveData;
82
83 enum {
84         PROP_0,
85         PROP_STORE,
86         PROP_LIST_FEATURES,
87         PROP_CONTACT_FEATURES,
88 };
89
90 enum DndDragType {
91         DND_DRAG_TYPE_CONTACT_ID,
92         DND_DRAG_TYPE_URL,
93         DND_DRAG_TYPE_STRING,
94 };
95
96 static const GtkTargetEntry drag_types_dest[] = {
97         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
98         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
99         { "text/plain",      0, DND_DRAG_TYPE_STRING },
100         { "STRING",          0, DND_DRAG_TYPE_STRING },
101 };
102
103 static const GtkTargetEntry drag_types_source[] = {
104         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
105 };
106
107 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
108 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
109
110 enum {
111         DRAG_CONTACT_RECEIVED,
112         LAST_SIGNAL
113 };
114
115 static guint signals[LAST_SIGNAL];
116
117 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
118
119 static void
120 contact_list_view_tooltip_destroy_cb (GtkWidget              *widget,
121                                       EmpathyContactListView *view)
122 {
123         EmpathyContactListViewPriv *priv = GET_PRIV (view);
124         
125         if (priv->tooltip_widget) {
126                 DEBUG ("Tooltip destroyed");
127                 g_object_unref (priv->tooltip_widget);
128                 priv->tooltip_widget = NULL;
129         }
130 }
131
132 static gboolean
133 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
134                                     gint                    x,
135                                     gint                    y,
136                                     gboolean                keyboard_mode,
137                                     GtkTooltip             *tooltip,
138                                     gpointer                user_data)
139 {
140         EmpathyContactListViewPriv *priv = GET_PRIV (view);
141         EmpathyContact             *contact;
142         GtkTreeModel               *model;
143         GtkTreeIter                 iter;
144         GtkTreePath                *path;
145         static gint                 running = 0;
146         gboolean                    ret = FALSE;
147
148         /* Avoid an infinite loop. See GNOME bug #574377 */
149         if (running > 0) {
150                 return FALSE;
151         }
152         running++;
153
154         if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
155                                                 keyboard_mode,
156                                                 &model, &path, &iter)) {
157                 goto OUT;
158         }
159
160         gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
161         gtk_tree_path_free (path);
162
163         gtk_tree_model_get (model, &iter,
164                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
165                             -1);
166         if (!contact) {
167                 goto OUT;
168         }
169
170         if (!priv->tooltip_widget) {
171                 priv->tooltip_widget = empathy_contact_widget_new (contact,
172                         EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP);
173                 g_object_ref (priv->tooltip_widget);
174                 g_signal_connect (priv->tooltip_widget, "destroy",
175                                   G_CALLBACK (contact_list_view_tooltip_destroy_cb),
176                                   view);
177                 gtk_widget_show (priv->tooltip_widget);
178         } else {
179                 empathy_contact_widget_set_contact (priv->tooltip_widget,
180                                                     contact);
181         }
182
183         gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
184         ret = TRUE;
185
186         g_object_unref (contact);
187 OUT:
188         running--;
189
190         return ret;
191 }
192
193 typedef struct {
194         gchar *new_group;
195         gchar *old_group;
196         GdkDragAction action;
197 } DndGetContactData;
198
199 static void
200 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
201 {
202         g_free (data->new_group);
203         g_free (data->old_group);
204         g_slice_free (DndGetContactData, data);
205 }
206
207 static void
208 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
209                                     GList                   *contacts,
210                                     gpointer                 user_data,
211                                     GObject                 *view)
212 {
213         EmpathyContactListViewPriv *priv = GET_PRIV (view);
214         DndGetContactData          *data = user_data;
215         EmpathyContactList         *list;
216         EmpathyContact             *contact = contacts->data;
217
218
219         DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
220                 empathy_contact_get_id (contact),
221                 empathy_contact_get_handle (contact),
222                 data->old_group, data->new_group);
223
224         list = empathy_contact_list_store_get_list_iface (priv->store);
225         if (data->new_group) {
226                 empathy_contact_list_add_to_group (list, contact, data->new_group);
227         }
228         if (data->old_group && data->action == GDK_ACTION_MOVE) {       
229                 empathy_contact_list_remove_from_group (list, contact, data->old_group);
230         }
231 }
232
233 static void
234 contact_list_view_drag_data_received (GtkWidget         *view,
235                                       GdkDragContext    *context,
236                                       gint               x,
237                                       gint               y,
238                                       GtkSelectionData  *selection,
239                                       guint              info,
240                                       guint              time)
241 {
242         EmpathyContactListViewPriv *priv;
243         EmpathyAccountManager      *account_manager;
244         EmpathyTpContactFactory    *factory = NULL;
245         McAccount                  *account;
246         GtkTreeModel               *model;
247         GtkTreeViewDropPosition     position;
248         GtkTreePath                *path;
249         const gchar                *id;
250         gchar                     **strv = NULL;
251         const gchar                *account_id;
252         const gchar                *contact_id;
253         gchar                      *new_group = NULL;
254         gchar                      *old_group = NULL;
255         DndGetContactData          *data;
256         gboolean                    is_row;
257         gboolean                    success = TRUE;
258
259         priv = GET_PRIV (view);
260         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
261
262         /* Get destination group information. */
263         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
264                                                     x,
265                                                     y,
266                                                     &path,
267                                                     &position);
268
269         if (is_row) {
270                 new_group = empathy_contact_list_store_get_parent_group (model,
271                         path, NULL);
272                 gtk_tree_path_free (path);
273         }
274
275         /* Get source group information. */
276         if (priv->drag_row) {
277                 path = gtk_tree_row_reference_get_path (priv->drag_row);
278                 if (path) {
279                         old_group = empathy_contact_list_store_get_parent_group (
280                                 model, path, NULL);
281                         gtk_tree_path_free (path);
282                 }
283         }
284
285         if (!tp_strdiff (old_group, new_group)) {
286                 g_free (new_group);
287                 g_free (old_group);
288                 goto OUT;
289         }
290
291         id = (const gchar*) selection->data;
292         DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'",
293                 context->action == GDK_ACTION_MOVE ? "move" : "",
294                 context->action == GDK_ACTION_COPY ? "copy" : "",
295                 id);
296
297         strv = g_strsplit (id, "/", 2);
298         account_id = strv[0];
299         contact_id = strv[1];
300         account = mc_account_lookup (account_id);
301         if (account) {
302                 TpConnection *connection;
303
304                 /* FIXME: We assume we have already an account manager */
305                 account_manager = empathy_account_manager_dup_singleton ();
306                 connection = empathy_account_manager_get_connection (account_manager,
307                                                                      account);
308                 if (connection) {
309                         factory = empathy_tp_contact_factory_dup_singleton (connection);
310                 }
311                 g_object_unref (account_manager);
312         }
313
314         if (!factory) {
315                 DEBUG ("Failed to get factory for account '%s'", account_id);
316                 success = FALSE;
317                 g_free (new_group);
318                 g_free (old_group);
319                 goto OUT;
320         }
321
322         data = g_slice_new0 (DndGetContactData);
323         data->new_group = new_group;
324         data->old_group = old_group;
325         data->action = context->action;
326
327         empathy_tp_contact_factory_get_from_ids (factory, 1, &contact_id,
328                 contact_list_view_drag_got_contact,
329                 data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
330                 G_OBJECT (view));
331
332         g_object_unref (factory);
333
334 OUT:
335         g_strfreev (strv);
336         gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
337 }
338
339 static gboolean
340 contact_list_view_drag_motion_cb (DragMotionData *data)
341 {
342         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
343                                   data->path,
344                                   FALSE);
345
346         data->timeout_id = 0;
347
348         return FALSE;
349 }
350
351 static gboolean
352 contact_list_view_drag_motion (GtkWidget      *widget,
353                                GdkDragContext *context,
354                                gint            x,
355                                gint            y,
356                                guint           time)
357 {
358         static DragMotionData *dm = NULL;
359         GtkTreePath           *path;
360         gboolean               is_row;
361         gboolean               is_different = FALSE;
362         gboolean               cleanup = TRUE;
363
364         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
365                                                 x,
366                                                 y,
367                                                 &path,
368                                                 NULL,
369                                                 NULL,
370                                                 NULL);
371
372         cleanup &= (!dm);
373
374         if (is_row) {
375                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
376                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
377         } else {
378                 cleanup &= FALSE;
379         }
380
381         if (!is_different && !cleanup) {
382                 return TRUE;
383         }
384
385         if (dm) {
386                 gtk_tree_path_free (dm->path);
387                 if (dm->timeout_id) {
388                         g_source_remove (dm->timeout_id);
389                 }
390
391                 g_free (dm);
392
393                 dm = NULL;
394         }
395
396         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
397                 dm = g_new0 (DragMotionData, 1);
398
399                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
400                 dm->path = gtk_tree_path_copy (path);
401
402                 dm->timeout_id = g_timeout_add_seconds (1,
403                         (GSourceFunc) contact_list_view_drag_motion_cb,
404                         dm);
405         }
406
407         return TRUE;
408 }
409
410 static void
411 contact_list_view_drag_begin (GtkWidget      *widget,
412                               GdkDragContext *context)
413 {
414         EmpathyContactListViewPriv *priv;
415         GtkTreeSelection          *selection;
416         GtkTreeModel              *model;
417         GtkTreePath               *path;
418         GtkTreeIter                iter;
419
420         priv = GET_PRIV (widget);
421
422         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
423                                                                               context);
424
425         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
426         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
427                 return;
428         }
429
430         path = gtk_tree_model_get_path (model, &iter);
431         priv->drag_row = gtk_tree_row_reference_new (model, path);
432         gtk_tree_path_free (path);
433 }
434
435 static void
436 contact_list_view_drag_data_get (GtkWidget        *widget,
437                                  GdkDragContext   *context,
438                                  GtkSelectionData *selection,
439                                  guint             info,
440                                  guint             time)
441 {
442         EmpathyContactListViewPriv *priv;
443         GtkTreePath                *src_path;
444         GtkTreeIter                 iter;
445         GtkTreeModel               *model;
446         EmpathyContact             *contact;
447         McAccount                  *account;
448         const gchar                *contact_id;
449         const gchar                *account_id;
450         gchar                      *str;
451
452         priv = GET_PRIV (widget);
453
454         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
455         if (!priv->drag_row) {
456                 return;
457         }
458
459         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
460         if (!src_path) {
461                 return;
462         }
463
464         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
465                 gtk_tree_path_free (src_path);
466                 return;
467         }
468
469         gtk_tree_path_free (src_path);
470
471         contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
472         if (!contact) {
473                 return;
474         }
475
476         account = empathy_contact_get_account (contact);
477         account_id = mc_account_get_unique_name (account);
478         contact_id = empathy_contact_get_id (contact);
479         g_object_unref (contact);
480         str = g_strconcat (account_id, "/", contact_id, NULL);
481
482         switch (info) {
483         case DND_DRAG_TYPE_CONTACT_ID:
484                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
485                                         (guchar*)str, strlen (str) + 1);
486                 break;
487         }
488
489         g_free (str);
490 }
491
492 static void
493 contact_list_view_drag_end (GtkWidget      *widget,
494                             GdkDragContext *context)
495 {
496         EmpathyContactListViewPriv *priv;
497
498         priv = GET_PRIV (widget);
499
500         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
501                                                                             context);
502
503         if (priv->drag_row) {
504                 gtk_tree_row_reference_free (priv->drag_row);
505                 priv->drag_row = NULL;
506         }
507 }
508
509 static gboolean
510 contact_list_view_drag_drop (GtkWidget      *widget,
511                              GdkDragContext *drag_context,
512                              gint            x,
513                              gint            y,
514                              guint           time)
515 {
516         return FALSE;
517 }
518
519 typedef struct {
520         EmpathyContactListView *view;
521         guint                   button;
522         guint32                 time;
523 } MenuPopupData;
524
525 static gboolean
526 contact_list_view_popup_menu_idle_cb (gpointer user_data)
527 {
528         MenuPopupData *data = user_data;
529         GtkWidget     *menu;
530
531         menu = empathy_contact_list_view_get_contact_menu (data->view);
532         if (!menu) {
533                 menu = empathy_contact_list_view_get_group_menu (data->view);
534         }
535
536         if (menu) {
537                 gtk_widget_show (menu);
538                 gtk_menu_popup (GTK_MENU (menu),
539                                 NULL, NULL, NULL, NULL,
540                                 data->button, data->time);
541         }
542
543         g_slice_free (MenuPopupData, data);
544
545         return FALSE;
546 }
547
548 static gboolean
549 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
550                                          GdkEventButton         *event,
551                                          gpointer                user_data)
552 {
553         if (event->button == 3) {
554                 MenuPopupData *data;
555
556                 data = g_slice_new (MenuPopupData);
557                 data->view = view;
558                 data->button = event->button;
559                 data->time = event->time;
560                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
561         }
562
563         return FALSE;
564 }
565
566 static gboolean
567 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
568                                       GdkEventKey            *event,
569                                       gpointer                user_data)
570 {
571         if (event->keyval == GDK_Menu) {
572                 MenuPopupData *data;
573
574                 data = g_slice_new (MenuPopupData);
575                 data->view = view;
576                 data->button = 0;
577                 data->time = event->time;
578                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
579         }
580
581         return FALSE;
582 }
583
584 static void
585 contact_list_view_row_activated (GtkTreeView       *view,
586                                  GtkTreePath       *path,
587                                  GtkTreeViewColumn *column)
588 {
589         EmpathyContactListViewPriv *priv = GET_PRIV (view);
590         EmpathyContact             *contact;
591         GtkTreeModel               *model;
592         GtkTreeIter                 iter;
593
594         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
595                 return;
596         }
597
598         model = GTK_TREE_MODEL (priv->store);
599         gtk_tree_model_get_iter (model, &iter, path);
600         gtk_tree_model_get (model, &iter,
601                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
602                             -1);
603
604         if (contact) {
605                 DEBUG ("Starting a chat");
606                 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
607                 g_object_unref (contact);
608         }
609 }
610
611 static void
612 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
613                                      const gchar                    *path_string,
614                                      EmpathyContactListView         *view)
615 {
616         EmpathyContactListViewPriv *priv = GET_PRIV (view);
617         GtkTreeModel               *model;
618         GtkTreeIter                 iter;
619         EmpathyContact             *contact;
620
621         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
622                 return;
623         }
624
625         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
626         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
627                 return;
628         }
629
630         gtk_tree_model_get (model, &iter,
631                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
632                             -1);
633
634         if (contact) {
635                 EmpathyCallFactory *factory;
636
637                 factory = empathy_call_factory_get ();
638                 empathy_call_factory_new_call (factory, contact);
639
640                 g_object_unref (contact);
641         }
642 }
643
644 static void
645 contact_list_view_cell_set_background (EmpathyContactListView *view,
646                                        GtkCellRenderer       *cell,
647                                        gboolean               is_group,
648                                        gboolean               is_active)
649 {
650         GdkColor  color;
651         GtkStyle *style;
652
653         style = gtk_widget_get_style (GTK_WIDGET (view));
654
655         if (!is_group && is_active) {
656                 color = style->bg[GTK_STATE_SELECTED];
657
658                 /* Here we take the current theme colour and add it to
659                  * the colour for white and average the two. This
660                  * gives a colour which is inline with the theme but
661                  * slightly whiter.
662                  */
663                 color.red = (color.red + (style->white).red) / 2;
664                 color.green = (color.green + (style->white).green) / 2;
665                 color.blue = (color.blue + (style->white).blue) / 2;
666
667                 g_object_set (cell,
668                               "cell-background-gdk", &color,
669                               NULL);
670         } else {
671                 g_object_set (cell,
672                               "cell-background-gdk", NULL,
673                               NULL);
674         }
675 }
676
677 static void
678 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
679                                          GtkCellRenderer       *cell,
680                                          GtkTreeModel          *model,
681                                          GtkTreeIter           *iter,
682                                          EmpathyContactListView *view)
683 {
684         gchar    *icon_name;
685         gboolean  is_group;
686         gboolean  is_active;
687
688         gtk_tree_model_get (model, iter,
689                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
690                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
691                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
692                             -1);
693
694         g_object_set (cell,
695                       "visible", !is_group,
696                       "icon-name", icon_name,
697                       NULL);
698
699         g_free (icon_name);
700
701         contact_list_view_cell_set_background (view, cell, is_group, is_active);
702 }
703
704 static void
705 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
706                                        GtkCellRenderer        *cell,
707                                        GtkTreeModel           *model,
708                                        GtkTreeIter            *iter,
709                                        EmpathyContactListView *view)
710 {
711         gboolean is_group;
712         gboolean is_active;
713         gboolean can_voip;
714
715         gtk_tree_model_get (model, iter,
716                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
717                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
718                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
719                             -1);
720
721         g_object_set (cell,
722                       "visible", !is_group && can_voip,
723                       "icon-name", EMPATHY_IMAGE_VOIP,
724                       NULL);
725
726         contact_list_view_cell_set_background (view, cell, is_group, is_active);
727 }
728
729 static void
730 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
731                                          GtkCellRenderer       *cell,
732                                          GtkTreeModel          *model,
733                                          GtkTreeIter           *iter,
734                                          EmpathyContactListView *view)
735 {
736         GdkPixbuf *pixbuf;
737         gboolean   show_avatar;
738         gboolean   is_group;
739         gboolean   is_active;
740
741         gtk_tree_model_get (model, iter,
742                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
743                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
744                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
745                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
746                             -1);
747
748         g_object_set (cell,
749                       "visible", !is_group && show_avatar,
750                       "pixbuf", pixbuf,
751                       NULL);
752
753         if (pixbuf) {
754                 g_object_unref (pixbuf);
755         }
756
757         contact_list_view_cell_set_background (view, cell, is_group, is_active);
758 }
759
760 static void
761 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
762                                        GtkCellRenderer       *cell,
763                                        GtkTreeModel          *model,
764                                        GtkTreeIter           *iter,
765                                        EmpathyContactListView *view)
766 {
767         gboolean is_group;
768         gboolean is_active;
769         gboolean show_status;
770
771         gtk_tree_model_get (model, iter,
772                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
773                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
774                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
775                             -1);
776
777         g_object_set (cell,
778                       "show-status", show_status,
779                       NULL);
780
781         contact_list_view_cell_set_background (view, cell, is_group, is_active);
782 }
783
784 static void
785 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
786                                            GtkCellRenderer       *cell,
787                                            GtkTreeModel          *model,
788                                            GtkTreeIter           *iter,
789                                            EmpathyContactListView *view)
790 {
791         gboolean is_group;
792         gboolean is_active;
793
794         gtk_tree_model_get (model, iter,
795                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
796                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
797                             -1);
798
799         if (gtk_tree_model_iter_has_child (model, iter)) {
800                 GtkTreePath *path;
801                 gboolean     row_expanded;
802
803                 path = gtk_tree_model_get_path (model, iter);
804                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
805                 gtk_tree_path_free (path);
806
807                 g_object_set (cell,
808                               "visible", TRUE,
809                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
810                               NULL);
811         } else {
812                 g_object_set (cell, "visible", FALSE, NULL);
813         }
814
815         contact_list_view_cell_set_background (view, cell, is_group, is_active);
816 }
817
818 static void
819 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
820                                              GtkTreeIter           *iter,
821                                              GtkTreePath           *path,
822                                              gpointer               user_data)
823 {
824         EmpathyContactListViewPriv *priv = GET_PRIV (view);
825         GtkTreeModel               *model;
826         gchar                      *name;
827         gboolean                    expanded;
828
829         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
830                 return;
831         }
832
833         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
834
835         gtk_tree_model_get (model, iter,
836                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
837                             -1);
838
839         expanded = GPOINTER_TO_INT (user_data);
840         empathy_contact_group_set_expanded (name, expanded);
841
842         g_free (name);
843 }
844
845 static void
846 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
847                                             GtkTreePath           *path,
848                                             GtkTreeIter           *iter,
849                                             EmpathyContactListView *view)
850 {
851         EmpathyContactListViewPriv *priv = GET_PRIV (view);
852         gboolean  is_group = FALSE;
853         gchar    *name = NULL;
854
855         gtk_tree_model_get (model, iter,
856                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
857                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
858                             -1);
859
860         if (!is_group || EMP_STR_EMPTY (name)) {
861                 g_free (name);
862                 return;
863         }
864
865         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
866             empathy_contact_group_get_expanded (name)) {
867                 g_signal_handlers_block_by_func (view,
868                                                  contact_list_view_row_expand_or_collapse_cb,
869                                                  GINT_TO_POINTER (TRUE));
870                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
871                 g_signal_handlers_unblock_by_func (view,
872                                                    contact_list_view_row_expand_or_collapse_cb,
873                                                    GINT_TO_POINTER (TRUE));
874         } else {
875                 g_signal_handlers_block_by_func (view,
876                                                  contact_list_view_row_expand_or_collapse_cb,
877                                                  GINT_TO_POINTER (FALSE));
878                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
879                 g_signal_handlers_unblock_by_func (view,
880                                                    contact_list_view_row_expand_or_collapse_cb,
881                                                    GINT_TO_POINTER (FALSE));
882         }
883
884         g_free (name);
885 }
886
887 static void
888 contact_list_view_setup (EmpathyContactListView *view)
889 {
890         EmpathyContactListViewPriv *priv;
891         GtkCellRenderer           *cell;
892         GtkTreeViewColumn         *col;
893         gint                       i;
894
895         priv = GET_PRIV (view);
896
897         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
898                                              empathy_contact_list_store_search_equal_func,
899                                              NULL, NULL);
900
901         g_signal_connect (priv->store, "row-has-child-toggled",
902                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
903                           view);
904         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
905                                  GTK_TREE_MODEL (priv->store));
906
907         /* Setup view */
908         g_object_set (view,
909                       "headers-visible", FALSE,
910                       "reorderable", TRUE,
911                       "show-expanders", FALSE,
912                       NULL);
913
914         col = gtk_tree_view_column_new ();
915
916         /* State */
917         cell = gtk_cell_renderer_pixbuf_new ();
918         gtk_tree_view_column_pack_start (col, cell, FALSE);
919         gtk_tree_view_column_set_cell_data_func (
920                 col, cell,
921                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
922                 view, NULL);
923
924         g_object_set (cell,
925                       "xpad", 5,
926                       "ypad", 1,
927                       "visible", FALSE,
928                       NULL);
929
930         /* Name */
931         cell = empathy_cell_renderer_text_new ();
932         gtk_tree_view_column_pack_start (col, cell, TRUE);
933         gtk_tree_view_column_set_cell_data_func (
934                 col, cell,
935                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
936                 view, NULL);
937
938         gtk_tree_view_column_add_attribute (col, cell,
939                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
940         gtk_tree_view_column_add_attribute (col, cell,
941                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
942         gtk_tree_view_column_add_attribute (col, cell,
943                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
944
945         /* Voip Capability Icon */
946         cell = empathy_cell_renderer_activatable_new ();
947         gtk_tree_view_column_pack_start (col, cell, FALSE);
948         gtk_tree_view_column_set_cell_data_func (
949                 col, cell,
950                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
951                 view, NULL);
952
953         g_object_set (cell,
954                       "visible", FALSE,
955                       NULL);
956
957         g_signal_connect (cell, "path-activated",
958                           G_CALLBACK (contact_list_view_voip_activated_cb),
959                           view);
960
961         /* Avatar */
962         cell = gtk_cell_renderer_pixbuf_new ();
963         gtk_tree_view_column_pack_start (col, cell, FALSE);
964         gtk_tree_view_column_set_cell_data_func (
965                 col, cell,
966                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
967                 view, NULL);
968
969         g_object_set (cell,
970                       "xpad", 0,
971                       "ypad", 0,
972                       "visible", FALSE,
973                       "width", 32,
974                       "height", 32,
975                       NULL);
976
977         /* Expander */
978         cell = empathy_cell_renderer_expander_new ();
979         gtk_tree_view_column_pack_end (col, cell, FALSE);
980         gtk_tree_view_column_set_cell_data_func (
981                 col, cell,
982                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
983                 view, NULL);
984
985         /* Actually add the column now we have added all cell renderers */
986         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
987
988         /* Drag & Drop. */
989         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
990                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
991                                                       FALSE);
992         }
993
994         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
995                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
996                                                         FALSE);
997         }
998 }
999
1000 static void
1001 contact_list_view_set_list_features (EmpathyContactListView         *view,
1002                                      EmpathyContactListFeatureFlags  features)
1003 {
1004         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1005         gboolean                    has_tooltip;
1006
1007         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1008
1009         priv->list_features = features;
1010
1011         /* Update DnD source/dest */
1012         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1013                 gtk_drag_source_set (GTK_WIDGET (view),
1014                                      GDK_BUTTON1_MASK,
1015                                      drag_types_source,
1016                                      G_N_ELEMENTS (drag_types_source),
1017                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1018         } else {
1019                 gtk_drag_source_unset (GTK_WIDGET (view));
1020
1021         }
1022
1023         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1024                 gtk_drag_dest_set (GTK_WIDGET (view),
1025                                    GTK_DEST_DEFAULT_ALL,
1026                                    drag_types_dest,
1027                                    G_N_ELEMENTS (drag_types_dest),
1028                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1029         } else {
1030                 /* FIXME: URI could still be droped depending on FT feature */
1031                 gtk_drag_dest_unset (GTK_WIDGET (view));
1032         }
1033
1034         /* Update has-tooltip */
1035         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1036         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1037 }
1038
1039 static void
1040 contact_list_view_finalize (GObject *object)
1041 {
1042         EmpathyContactListViewPriv *priv;
1043
1044         priv = GET_PRIV (object);
1045
1046         if (priv->store) {
1047                 g_object_unref (priv->store);
1048         }
1049         if (priv->tooltip_widget) {
1050                 gtk_widget_destroy (priv->tooltip_widget);
1051         }
1052
1053         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1054 }
1055
1056 static void
1057 contact_list_view_get_property (GObject    *object,
1058                                 guint       param_id,
1059                                 GValue     *value,
1060                                 GParamSpec *pspec)
1061 {
1062         EmpathyContactListViewPriv *priv;
1063
1064         priv = GET_PRIV (object);
1065
1066         switch (param_id) {
1067         case PROP_STORE:
1068                 g_value_set_object (value, priv->store);
1069                 break;
1070         case PROP_LIST_FEATURES:
1071                 g_value_set_flags (value, priv->list_features);
1072                 break;
1073         case PROP_CONTACT_FEATURES:
1074                 g_value_set_flags (value, priv->contact_features);
1075                 break;
1076         default:
1077                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1078                 break;
1079         };
1080 }
1081
1082 static void
1083 contact_list_view_set_property (GObject      *object,
1084                                 guint         param_id,
1085                                 const GValue *value,
1086                                 GParamSpec   *pspec)
1087 {
1088         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1089         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1090
1091         switch (param_id) {
1092         case PROP_STORE:
1093                 priv->store = g_value_dup_object (value);
1094                 contact_list_view_setup (view);
1095                 break;
1096         case PROP_LIST_FEATURES:
1097                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1098                 break;
1099         case PROP_CONTACT_FEATURES:
1100                 priv->contact_features = g_value_get_flags (value);
1101                 break;
1102         default:
1103                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1104                 break;
1105         };
1106 }
1107
1108 static void
1109 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1110 {
1111         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1112         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1113         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1114
1115         object_class->finalize = contact_list_view_finalize;
1116         object_class->get_property = contact_list_view_get_property;
1117         object_class->set_property = contact_list_view_set_property;
1118
1119         widget_class->drag_data_received = contact_list_view_drag_data_received;
1120         widget_class->drag_drop          = contact_list_view_drag_drop;
1121         widget_class->drag_begin         = contact_list_view_drag_begin;
1122         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1123         widget_class->drag_end           = contact_list_view_drag_end;
1124         widget_class->drag_motion        = contact_list_view_drag_motion;
1125
1126         /* We use the class method to let user of this widget to connect to
1127          * the signal and stop emission of the signal so the default handler
1128          * won't be called. */
1129         tree_view_class->row_activated = contact_list_view_row_activated;
1130
1131         signals[DRAG_CONTACT_RECEIVED] =
1132                 g_signal_new ("drag-contact-received",
1133                               G_OBJECT_CLASS_TYPE (klass),
1134                               G_SIGNAL_RUN_LAST,
1135                               0,
1136                               NULL, NULL,
1137                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1138                               G_TYPE_NONE,
1139                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1140
1141         g_object_class_install_property (object_class,
1142                                          PROP_STORE,
1143                                          g_param_spec_object ("store",
1144                                                              "The store of the view",
1145                                                              "The store of the view",
1146                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1147                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1148         g_object_class_install_property (object_class,
1149                                          PROP_LIST_FEATURES,
1150                                          g_param_spec_flags ("list-features",
1151                                                              "Features of the view",
1152                                                              "Falgs for all enabled features",
1153                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1154                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1155                                                               G_PARAM_READWRITE));
1156         g_object_class_install_property (object_class,
1157                                          PROP_CONTACT_FEATURES,
1158                                          g_param_spec_flags ("contact-features",
1159                                                              "Features of the contact menu",
1160                                                              "Falgs for all enabled features for the menu",
1161                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1162                                                               EMPATHY_CONTACT_FEATURE_NONE,
1163                                                               G_PARAM_READWRITE));
1164
1165         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1166 }
1167
1168 static void
1169 empathy_contact_list_view_init (EmpathyContactListView *view)
1170 {
1171         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1172                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1173
1174         view->priv = priv;
1175         /* Get saved group states. */
1176         empathy_contact_groups_get_all ();
1177
1178         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
1179                                               empathy_contact_list_store_row_separator_func,
1180                                               NULL, NULL);
1181
1182         /* Connect to tree view signals rather than override. */
1183         g_signal_connect (view, "button-press-event",
1184                           G_CALLBACK (contact_list_view_button_press_event_cb),
1185                           NULL);
1186         g_signal_connect (view, "key-press-event",
1187                           G_CALLBACK (contact_list_view_key_press_event_cb),
1188                           NULL);
1189         g_signal_connect (view, "row-expanded",
1190                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1191                           GINT_TO_POINTER (TRUE));
1192         g_signal_connect (view, "row-collapsed",
1193                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1194                           GINT_TO_POINTER (FALSE));
1195         g_signal_connect (view, "query-tooltip",
1196                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1197                           NULL);
1198 }
1199
1200 EmpathyContactListView *
1201 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1202                                EmpathyContactListFeatureFlags  list_features,
1203                                EmpathyContactFeatureFlags      contact_features)
1204 {
1205         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1206         
1207         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1208                              "store", store,
1209                              "contact-features", contact_features,
1210                              "list-features", list_features,
1211                              NULL);
1212 }
1213
1214 EmpathyContact *
1215 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
1216 {
1217         EmpathyContactListViewPriv *priv;
1218         GtkTreeSelection          *selection;
1219         GtkTreeIter                iter;
1220         GtkTreeModel              *model;
1221         EmpathyContact             *contact;
1222
1223         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1224
1225         priv = GET_PRIV (view);
1226
1227         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1228         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1229                 return NULL;
1230         }
1231
1232         gtk_tree_model_get (model, &iter,
1233                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1234                             -1);
1235
1236         return contact;
1237 }
1238
1239 gchar *
1240 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1241 {
1242         EmpathyContactListViewPriv *priv;
1243         GtkTreeSelection          *selection;
1244         GtkTreeIter                iter;
1245         GtkTreeModel              *model;
1246         gboolean                   is_group;
1247         gchar                     *name;
1248
1249         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1250
1251         priv = GET_PRIV (view);
1252
1253         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1254         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1255                 return NULL;
1256         }
1257
1258         gtk_tree_model_get (model, &iter,
1259                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1260                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1261                             -1);
1262
1263         if (!is_group) {
1264                 g_free (name);
1265                 return NULL;
1266         }
1267
1268         return name;
1269 }
1270
1271 static gboolean
1272 contact_list_view_remove_dialog_show (GtkWindow   *parent, 
1273                                       const gchar *message, 
1274                                       const gchar *secondary_text)
1275 {
1276         GtkWidget *dialog;
1277         gboolean res;
1278         
1279         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1280                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1281                                          "%s", message);
1282         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1283                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1284                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1285                                 NULL);
1286         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1287                                                   "%s", secondary_text);
1288          
1289         gtk_widget_show (dialog);
1290          
1291         res = gtk_dialog_run (GTK_DIALOG (dialog));
1292         gtk_widget_destroy (dialog);
1293
1294         return (res == GTK_RESPONSE_YES);
1295 }
1296
1297 static void
1298 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1299                                             EmpathyContactListView *view)
1300 {
1301         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1302         gchar                      *group;
1303
1304         group = empathy_contact_list_view_get_selected_group (view);
1305         if (group) {
1306                 gchar     *text;
1307                 GtkWindow *parent;
1308
1309                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1310                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1311                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1312                         EmpathyContactList *list;
1313
1314                         list = empathy_contact_list_store_get_list_iface (priv->store);
1315                         empathy_contact_list_remove_group (list, group);
1316                 }
1317
1318                 g_free (text);
1319         }
1320
1321         g_free (group);
1322 }
1323
1324 GtkWidget *
1325 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1326 {
1327         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1328         gchar                      *group;
1329         GtkWidget                  *menu;
1330         GtkWidget                  *item;
1331         GtkWidget                  *image;
1332
1333         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1334
1335         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1336                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1337                 return NULL;
1338         }
1339
1340         group = empathy_contact_list_view_get_selected_group (view);
1341         if (!group) {
1342                 return NULL;
1343         }
1344
1345         menu = gtk_menu_new ();
1346
1347         /* FIXME: Not implemented yet
1348         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1349                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1350                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1351                 gtk_widget_show (item);
1352                 g_signal_connect (item, "activate",
1353                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1354                                   view);
1355         }*/
1356
1357         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1358                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1359                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1360                                                       GTK_ICON_SIZE_MENU);
1361                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1362                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1363                 gtk_widget_show (item);
1364                 g_signal_connect (item, "activate",
1365                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1366                                   view);
1367         }
1368
1369         g_free (group);
1370
1371         return menu;
1372 }
1373
1374 static void
1375 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1376                                       EmpathyContactListView *view)
1377 {
1378         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1379         EmpathyContact             *contact;
1380                 
1381         contact = empathy_contact_list_view_get_selected (view);
1382
1383         if (contact) {
1384                 gchar     *text; 
1385                 GtkWindow *parent;
1386
1387                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1388                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1389                                         empathy_contact_get_name (contact));                                            
1390                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1391                         EmpathyContactList *list;
1392
1393                         list = empathy_contact_list_store_get_list_iface (priv->store);
1394                         empathy_contact_list_remove (list, contact, 
1395                                 _("Sorry, I don't want you in my contact list anymore."));
1396                 }
1397
1398                 g_free (text);
1399                 g_object_unref (contact);
1400         }
1401 }
1402
1403 GtkWidget *
1404 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1405 {
1406         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1407         EmpathyContact             *contact;
1408         GtkWidget                  *menu;
1409         GtkWidget                  *item;
1410         GtkWidget                  *image;
1411
1412         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1413
1414         contact = empathy_contact_list_view_get_selected (view);
1415         if (!contact) {
1416                 return NULL;
1417         }
1418
1419         menu = empathy_contact_menu_new (contact, priv->contact_features);
1420
1421         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1422                 g_object_unref (contact);
1423                 return menu;
1424         }
1425
1426         if (menu) {
1427                 /* Separator */
1428                 item = gtk_separator_menu_item_new ();
1429                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1430                 gtk_widget_show (item);
1431         } else {
1432                 menu = gtk_menu_new ();
1433         }
1434
1435         /* Remove contact */
1436         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1437                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1438                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1439                                                       GTK_ICON_SIZE_MENU);
1440                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1441                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1442                 gtk_widget_show (item);
1443                 g_signal_connect (item, "activate",
1444                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1445                                   view);
1446         }
1447
1448         g_object_unref (contact);
1449
1450         return menu;
1451 }
1452