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