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