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