]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Do not add a separator on the contact menu if there is no remove feature.
[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                 gtk_widget_show (menu);
412                 gtk_menu_popup (GTK_MENU (menu),
413                                 NULL, NULL, NULL, NULL,
414                                 data->button, data->time);
415         }
416
417         g_slice_free (MenuPopupData, data);
418
419         return FALSE;
420 }
421
422 static gboolean
423 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
424                                          GdkEventButton         *event,
425                                          gpointer                user_data)
426 {
427         if (event->button == 3) {
428                 MenuPopupData *data;
429
430                 data = g_slice_new (MenuPopupData);
431                 data->view = view;
432                 data->button = event->button;
433                 data->time = event->time;
434                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
435         }
436
437         return FALSE;
438 }
439
440 static gboolean
441 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
442                                       GdkEventKey            *event,
443                                       gpointer                user_data)
444 {
445         if (event->keyval == GDK_Menu) {
446                 MenuPopupData *data;
447
448                 data = g_slice_new (MenuPopupData);
449                 data->view = view;
450                 data->button = 0;
451                 data->time = event->time;
452                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
453         }
454
455         return FALSE;
456 }
457
458 static void
459 contact_list_view_row_activated_cb (EmpathyContactListView *view,
460                                     GtkTreePath            *path,
461                                     GtkTreeViewColumn      *col,
462                                     gpointer                user_data)
463 {
464         EmpathyContactListViewPriv *priv = GET_PRIV (view);
465         EmpathyContact             *contact;
466         GtkTreeModel               *model;
467         GtkTreeIter                 iter;
468
469         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
470                 return;
471         }
472
473         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
474
475         gtk_tree_model_get_iter (model, &iter, path);
476         gtk_tree_model_get (model, &iter,
477                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
478                             -1);
479
480         if (contact) {
481                 empathy_dispatcher_chat_with_contact (contact);
482                 g_object_unref (contact);
483         }
484 }
485
486 static void
487 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
488                                      const gchar                    *path_string,
489                                      EmpathyContactListView         *view)
490 {
491         EmpathyContactListViewPriv *priv = GET_PRIV (view);
492         GtkTreeModel               *model;
493         GtkTreeIter                 iter;
494         EmpathyContact             *contact;
495
496         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
497                 return;
498         }
499
500         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
501         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
502                 return;
503         }
504
505         gtk_tree_model_get (model, &iter,
506                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
507                             -1);
508
509         if (contact) {
510                 empathy_dispatcher_call_with_contact (contact);
511                 g_object_unref (contact);
512         }
513 }
514
515 static void
516 contact_list_view_cell_set_background (EmpathyContactListView *view,
517                                        GtkCellRenderer       *cell,
518                                        gboolean               is_group,
519                                        gboolean               is_active)
520 {
521         GdkColor  color;
522         GtkStyle *style;
523
524         style = gtk_widget_get_style (GTK_WIDGET (view));
525
526         if (!is_group && is_active) {
527                 color = style->bg[GTK_STATE_SELECTED];
528
529                 /* Here we take the current theme colour and add it to
530                  * the colour for white and average the two. This
531                  * gives a colour which is inline with the theme but
532                  * slightly whiter.
533                  */
534                 color.red = (color.red + (style->white).red) / 2;
535                 color.green = (color.green + (style->white).green) / 2;
536                 color.blue = (color.blue + (style->white).blue) / 2;
537
538                 g_object_set (cell,
539                               "cell-background-gdk", &color,
540                               NULL);
541         } else {
542                 g_object_set (cell,
543                               "cell-background-gdk", NULL,
544                               NULL);
545         }
546 }
547
548 static void
549 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
550                                          GtkCellRenderer       *cell,
551                                          GtkTreeModel          *model,
552                                          GtkTreeIter           *iter,
553                                          EmpathyContactListView *view)
554 {
555         gchar    *icon_name;
556         gboolean  is_group;
557         gboolean  is_active;
558
559         gtk_tree_model_get (model, iter,
560                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
561                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
562                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
563                             -1);
564
565         g_object_set (cell,
566                       "visible", !is_group,
567                       "icon-name", icon_name,
568                       NULL);
569
570         g_free (icon_name);
571
572         contact_list_view_cell_set_background (view, cell, is_group, is_active);
573 }
574
575 static void
576 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
577                                        GtkCellRenderer        *cell,
578                                        GtkTreeModel           *model,
579                                        GtkTreeIter            *iter,
580                                        EmpathyContactListView *view)
581 {
582         gboolean is_group;
583         gboolean is_active;
584         gboolean can_voip;
585
586         gtk_tree_model_get (model, iter,
587                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
588                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
589                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
590                             -1);
591
592         g_object_set (cell,
593                       "visible", !is_group && can_voip,
594                       "icon-name", EMPATHY_IMAGE_VOIP,
595                       NULL);
596
597         contact_list_view_cell_set_background (view, cell, is_group, is_active);
598 }
599
600 static void
601 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
602                                          GtkCellRenderer       *cell,
603                                          GtkTreeModel          *model,
604                                          GtkTreeIter           *iter,
605                                          EmpathyContactListView *view)
606 {
607         GdkPixbuf *pixbuf;
608         gboolean   show_avatar;
609         gboolean   is_group;
610         gboolean   is_active;
611
612         gtk_tree_model_get (model, iter,
613                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
614                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
615                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
616                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
617                             -1);
618
619         g_object_set (cell,
620                       "visible", !is_group && show_avatar,
621                       "pixbuf", pixbuf,
622                       NULL);
623
624         if (pixbuf) {
625                 g_object_unref (pixbuf);
626         }
627
628         contact_list_view_cell_set_background (view, cell, is_group, is_active);
629 }
630
631 static void
632 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
633                                        GtkCellRenderer       *cell,
634                                        GtkTreeModel          *model,
635                                        GtkTreeIter           *iter,
636                                        EmpathyContactListView *view)
637 {
638         gboolean is_group;
639         gboolean is_active;
640         gboolean show_status;
641
642         gtk_tree_model_get (model, iter,
643                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
644                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
645                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
646                             -1);
647
648         g_object_set (cell,
649                       "show-status", show_status,
650                       NULL);
651
652         contact_list_view_cell_set_background (view, cell, is_group, is_active);
653 }
654
655 static void
656 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
657                                            GtkCellRenderer       *cell,
658                                            GtkTreeModel          *model,
659                                            GtkTreeIter           *iter,
660                                            EmpathyContactListView *view)
661 {
662         gboolean is_group;
663         gboolean is_active;
664
665         gtk_tree_model_get (model, iter,
666                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
667                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
668                             -1);
669
670         if (gtk_tree_model_iter_has_child (model, iter)) {
671                 GtkTreePath *path;
672                 gboolean     row_expanded;
673
674                 path = gtk_tree_model_get_path (model, iter);
675                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
676                 gtk_tree_path_free (path);
677
678                 g_object_set (cell,
679                               "visible", TRUE,
680                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
681                               NULL);
682         } else {
683                 g_object_set (cell, "visible", FALSE, NULL);
684         }
685
686         contact_list_view_cell_set_background (view, cell, is_group, is_active);
687 }
688
689 static void
690 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
691                                              GtkTreeIter           *iter,
692                                              GtkTreePath           *path,
693                                              gpointer               user_data)
694 {
695         EmpathyContactListViewPriv *priv = GET_PRIV (view);
696         GtkTreeModel               *model;
697         gchar                      *name;
698         gboolean                    expanded;
699
700         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
701                 return;
702         }
703
704         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
705
706         gtk_tree_model_get (model, iter,
707                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
708                             -1);
709
710         expanded = GPOINTER_TO_INT (user_data);
711         empathy_contact_group_set_expanded (name, expanded);
712
713         g_free (name);
714 }
715
716 static void
717 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
718                                             GtkTreePath           *path,
719                                             GtkTreeIter           *iter,
720                                             EmpathyContactListView *view)
721 {
722         EmpathyContactListViewPriv *priv = GET_PRIV (view);
723         gboolean  is_group = FALSE;
724         gchar    *name = NULL;
725
726         gtk_tree_model_get (model, iter,
727                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
728                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
729                             -1);
730
731         if (!is_group || G_STR_EMPTY (name)) {
732                 g_free (name);
733                 return;
734         }
735
736         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
737             empathy_contact_group_get_expanded (name)) {
738                 g_signal_handlers_block_by_func (view,
739                                                  contact_list_view_row_expand_or_collapse_cb,
740                                                  GINT_TO_POINTER (TRUE));
741                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
742                 g_signal_handlers_unblock_by_func (view,
743                                                    contact_list_view_row_expand_or_collapse_cb,
744                                                    GINT_TO_POINTER (TRUE));
745         } else {
746                 g_signal_handlers_block_by_func (view,
747                                                  contact_list_view_row_expand_or_collapse_cb,
748                                                  GINT_TO_POINTER (FALSE));
749                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
750                 g_signal_handlers_unblock_by_func (view,
751                                                    contact_list_view_row_expand_or_collapse_cb,
752                                                    GINT_TO_POINTER (FALSE));
753         }
754
755         g_free (name);
756 }
757
758 static void
759 contact_list_view_setup (EmpathyContactListView *view)
760 {
761         EmpathyContactListViewPriv *priv;
762         GtkCellRenderer           *cell;
763         GtkTreeViewColumn         *col;
764         gint                       i;
765
766         priv = GET_PRIV (view);
767
768         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
769                                              empathy_contact_list_store_search_equal_func,
770                                              NULL, NULL);
771
772         g_signal_connect (priv->store, "row-has-child-toggled",
773                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
774                           view);
775         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
776                                  GTK_TREE_MODEL (priv->store));
777
778         /* Setup view */
779         g_object_set (view,
780                       "headers-visible", FALSE,
781                       "reorderable", TRUE,
782                       "show-expanders", FALSE,
783                       NULL);
784
785         col = gtk_tree_view_column_new ();
786
787         /* State */
788         cell = gtk_cell_renderer_pixbuf_new ();
789         gtk_tree_view_column_pack_start (col, cell, FALSE);
790         gtk_tree_view_column_set_cell_data_func (
791                 col, cell,
792                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
793                 view, NULL);
794
795         g_object_set (cell,
796                       "xpad", 5,
797                       "ypad", 1,
798                       "visible", FALSE,
799                       NULL);
800
801         /* Name */
802         cell = empathy_cell_renderer_text_new ();
803         gtk_tree_view_column_pack_start (col, cell, TRUE);
804         gtk_tree_view_column_set_cell_data_func (
805                 col, cell,
806                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
807                 view, NULL);
808
809         gtk_tree_view_column_add_attribute (col, cell,
810                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
811         gtk_tree_view_column_add_attribute (col, cell,
812                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
813         gtk_tree_view_column_add_attribute (col, cell,
814                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
815
816         /* Voip Capability Icon */
817         cell = empathy_cell_renderer_activatable_new ();
818         gtk_tree_view_column_pack_start (col, cell, FALSE);
819         gtk_tree_view_column_set_cell_data_func (
820                 col, cell,
821                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
822                 view, NULL);
823
824         g_object_set (cell,
825                       "visible", FALSE,
826                       NULL);
827
828         g_signal_connect (cell, "path-activated",
829                           G_CALLBACK (contact_list_view_voip_activated_cb),
830                           view);
831
832         /* Avatar */
833         cell = gtk_cell_renderer_pixbuf_new ();
834         gtk_tree_view_column_pack_start (col, cell, FALSE);
835         gtk_tree_view_column_set_cell_data_func (
836                 col, cell,
837                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
838                 view, NULL);
839
840         g_object_set (cell,
841                       "xpad", 0,
842                       "ypad", 0,
843                       "visible", FALSE,
844                       "width", 32,
845                       "height", 32,
846                       NULL);
847
848         /* Expander */
849         cell = empathy_cell_renderer_expander_new ();
850         gtk_tree_view_column_pack_end (col, cell, FALSE);
851         gtk_tree_view_column_set_cell_data_func (
852                 col, cell,
853                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
854                 view, NULL);
855
856         /* Actually add the column now we have added all cell renderers */
857         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
858
859         /* Drag & Drop. */
860         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
861                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
862                                                       FALSE);
863         }
864
865         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
866                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
867                                                         FALSE);
868         }
869 }
870
871 static void
872 contact_list_view_set_list_features (EmpathyContactListView         *view,
873                                      EmpathyContactListFeatureFlags  features)
874 {
875         EmpathyContactListViewPriv *priv = GET_PRIV (view);
876
877         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
878
879         priv->list_features = features;
880
881         /* Update DnD source/dest */
882         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
883                 gtk_drag_source_set (GTK_WIDGET (view),
884                                      GDK_BUTTON1_MASK,
885                                      drag_types_source,
886                                      G_N_ELEMENTS (drag_types_source),
887                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
888         } else {
889                 gtk_drag_source_unset (GTK_WIDGET (view));
890
891         }
892
893         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
894                 gtk_drag_dest_set (GTK_WIDGET (view),
895                                    GTK_DEST_DEFAULT_ALL,
896                                    drag_types_dest,
897                                    G_N_ELEMENTS (drag_types_dest),
898                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
899         } else {
900                 /* FIXME: URI could still be  droped depending on FT feature */
901                 gtk_drag_dest_unset (GTK_WIDGET (view));
902         }
903 }
904
905 static void
906 contact_list_view_finalize (GObject *object)
907 {
908         EmpathyContactListViewPriv *priv;
909
910         priv = GET_PRIV (object);
911
912         if (priv->store) {
913                 g_object_unref (priv->store);
914         }
915
916         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
917 }
918
919 static void
920 contact_list_view_get_property (GObject    *object,
921                                 guint       param_id,
922                                 GValue     *value,
923                                 GParamSpec *pspec)
924 {
925         EmpathyContactListViewPriv *priv;
926
927         priv = GET_PRIV (object);
928
929         switch (param_id) {
930         case PROP_STORE:
931                 g_value_set_object (value, priv->store);
932                 break;
933         case PROP_LIST_FEATURES:
934                 g_value_set_flags (value, priv->list_features);
935                 break;
936         case PROP_CONTACT_FEATURES:
937                 g_value_set_flags (value, priv->contact_features);
938                 break;
939         default:
940                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
941                 break;
942         };
943 }
944
945 static void
946 contact_list_view_set_property (GObject      *object,
947                                 guint         param_id,
948                                 const GValue *value,
949                                 GParamSpec   *pspec)
950 {
951         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
952         EmpathyContactListViewPriv *priv = GET_PRIV (object);
953
954         switch (param_id) {
955         case PROP_STORE:
956                 priv->store = g_value_dup_object (value);
957                 contact_list_view_setup (view);
958                 break;
959         case PROP_LIST_FEATURES:
960                 contact_list_view_set_list_features (view, g_value_get_flags (value));
961                 break;
962         case PROP_CONTACT_FEATURES:
963                 priv->contact_features = g_value_get_flags (value);
964                 break;
965         default:
966                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
967                 break;
968         };
969 }
970
971 static void
972 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
973 {
974         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
975         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
976
977         object_class->finalize = contact_list_view_finalize;
978         object_class->get_property = contact_list_view_get_property;
979         object_class->set_property = contact_list_view_set_property;
980
981         widget_class->drag_data_received = contact_list_view_drag_data_received;
982         widget_class->drag_drop          = contact_list_view_drag_drop;
983         widget_class->drag_begin         = contact_list_view_drag_begin;
984         widget_class->drag_data_get      = contact_list_view_drag_data_get;
985         widget_class->drag_end           = contact_list_view_drag_end;
986         /* FIXME: noticed but when you drag the row over the treeview
987          * fast, it seems to stop redrawing itself, if we don't
988          * connect this signal, all is fine.
989          */
990         widget_class->drag_motion        = contact_list_view_drag_motion;
991
992         signals[DRAG_CONTACT_RECEIVED] =
993                 g_signal_new ("drag-contact-received",
994                               G_OBJECT_CLASS_TYPE (klass),
995                               G_SIGNAL_RUN_LAST,
996                               0,
997                               NULL, NULL,
998                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
999                               G_TYPE_NONE,
1000                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1001
1002         g_object_class_install_property (object_class,
1003                                          PROP_STORE,
1004                                          g_param_spec_object ("store",
1005                                                              "The store of the view",
1006                                                              "The store of the view",
1007                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1008                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1009         g_object_class_install_property (object_class,
1010                                          PROP_LIST_FEATURES,
1011                                          g_param_spec_flags ("list-features",
1012                                                              "Features of the view",
1013                                                              "Falgs for all enabled features",
1014                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1015                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1016                                                               G_PARAM_READWRITE));
1017         g_object_class_install_property (object_class,
1018                                          PROP_CONTACT_FEATURES,
1019                                          g_param_spec_flags ("contact-features",
1020                                                              "Features of the contact menu",
1021                                                              "Falgs for all enabled features for the menu",
1022                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1023                                                               EMPATHY_CONTACT_FEATURE_NONE,
1024                                                               G_PARAM_READWRITE));
1025
1026         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1027 }
1028
1029 static void
1030 empathy_contact_list_view_init (EmpathyContactListView *view)
1031 {
1032         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1033                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1034
1035         view->priv = priv;
1036         /* Get saved group states. */
1037         empathy_contact_groups_get_all ();
1038
1039         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
1040                                               empathy_contact_list_store_row_separator_func,
1041                                               NULL, NULL);
1042
1043         /* Connect to tree view signals rather than override. */
1044         g_signal_connect (view,
1045                           "button-press-event",
1046                           G_CALLBACK (contact_list_view_button_press_event_cb),
1047                           NULL);
1048         g_signal_connect (view,
1049                           "key-press-event",
1050                           G_CALLBACK (contact_list_view_key_press_event_cb),
1051                           NULL);
1052         g_signal_connect (view,
1053                           "row-activated",
1054                           G_CALLBACK (contact_list_view_row_activated_cb),
1055                           NULL);
1056         g_signal_connect (view,
1057                           "row-expanded",
1058                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1059                           GINT_TO_POINTER (TRUE));
1060         g_signal_connect (view,
1061                           "row-collapsed",
1062                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1063                           GINT_TO_POINTER (FALSE));
1064 }
1065
1066 EmpathyContactListView *
1067 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1068                                EmpathyContactListFeatureFlags  list_features,
1069                                EmpathyContactFeatureFlags      contact_features)
1070 {
1071         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1072         
1073         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1074                              "store", store,
1075                              "contact-features", contact_features,
1076                              "list-features", list_features,
1077                              NULL);
1078 }
1079
1080 EmpathyContact *
1081 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
1082 {
1083         EmpathyContactListViewPriv *priv;
1084         GtkTreeSelection          *selection;
1085         GtkTreeIter                iter;
1086         GtkTreeModel              *model;
1087         EmpathyContact             *contact;
1088
1089         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1090
1091         priv = GET_PRIV (view);
1092
1093         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1094         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1095                 return NULL;
1096         }
1097
1098         gtk_tree_model_get (model, &iter,
1099                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1100                             -1);
1101
1102         return contact;
1103 }
1104
1105 gchar *
1106 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1107 {
1108         EmpathyContactListViewPriv *priv;
1109         GtkTreeSelection          *selection;
1110         GtkTreeIter                iter;
1111         GtkTreeModel              *model;
1112         gboolean                   is_group;
1113         gchar                     *name;
1114
1115         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1116
1117         priv = GET_PRIV (view);
1118
1119         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1120         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1121                 return NULL;
1122         }
1123
1124         gtk_tree_model_get (model, &iter,
1125                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1126                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1127                             -1);
1128
1129         if (!is_group) {
1130                 g_free (name);
1131                 return NULL;
1132         }
1133
1134         return name;
1135 }
1136
1137 static gboolean
1138 contact_list_view_remove_dialog_show (GtkWindow   *parent, 
1139                                       const gchar *window_title, 
1140                                       const gchar *text)
1141 {
1142         GtkWidget *dialog, *label, *image, *hbox;
1143         gboolean res;
1144         
1145         dialog = gtk_dialog_new_with_buttons (window_title, parent,
1146                                               GTK_DIALOG_MODAL,
1147                                               GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1148                                               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1149                                               NULL);
1150         gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
1151          
1152         label = gtk_label_new (text);
1153         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1154          
1155         hbox = gtk_hbox_new (FALSE, 5);
1156         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1157         gtk_box_pack_start_defaults (GTK_BOX (hbox), image);
1158         gtk_box_pack_start_defaults (GTK_BOX (hbox), label);     
1159         gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
1160
1161         gtk_widget_show (image);
1162         gtk_widget_show (label);
1163         gtk_widget_show (hbox);
1164         gtk_widget_show (dialog);
1165          
1166         res = gtk_dialog_run (GTK_DIALOG (dialog));
1167         gtk_widget_destroy (dialog);
1168
1169         return (res == GTK_RESPONSE_YES);
1170 }
1171
1172 static void
1173 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1174                                             EmpathyContactListView *view)
1175 {
1176         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1177         gchar                      *group;
1178
1179         group = empathy_contact_list_view_get_selected_group (view);
1180         if (group) {
1181                 gchar     *text;
1182                 GtkWindow *parent;
1183
1184                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1185                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1186                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1187                         EmpathyContactList *list;
1188
1189                         list = empathy_contact_list_store_get_list_iface (priv->store);
1190                         empathy_contact_list_remove_group (list, group);
1191                 }
1192
1193                 g_free (text);
1194         }
1195
1196         g_free (group);
1197 }
1198
1199 GtkWidget *
1200 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1201 {
1202         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1203         gchar                      *group;
1204         GtkWidget                  *menu;
1205         GtkWidget                  *item;
1206         GtkWidget                  *image;
1207
1208         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1209
1210         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1211                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1212                 return NULL;
1213         }
1214
1215         group = empathy_contact_list_view_get_selected_group (view);
1216         if (!group) {
1217                 return NULL;
1218         }
1219
1220         menu = gtk_menu_new ();
1221
1222         /* FIXME: Not implemented yet
1223         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1224                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1225                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1226                 gtk_widget_show (item);
1227                 g_signal_connect (item, "activate",
1228                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1229                                   view);
1230         }*/
1231
1232         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1233                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1234                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1235                                                       GTK_ICON_SIZE_MENU);
1236                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1237                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1238                 gtk_widget_show (item);
1239                 g_signal_connect (item, "activate",
1240                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1241                                   view);
1242         }
1243
1244         g_free (group);
1245
1246         return menu;
1247 }
1248
1249 static void
1250 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1251                                       EmpathyContactListView *view)
1252 {
1253         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1254         EmpathyContact             *contact;
1255                 
1256         contact = empathy_contact_list_view_get_selected (view);
1257
1258         if (contact) {
1259                 gchar     *text; 
1260                 GtkWindow *parent;
1261
1262                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1263                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1264                                         empathy_contact_get_name (contact));                                            
1265                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1266                         EmpathyContactList *list;
1267
1268                         list = empathy_contact_list_store_get_list_iface (priv->store);
1269                         empathy_contact_list_remove (list, contact, 
1270                                 _("Sorry, I don't want you in my contact list anymore."));
1271                 }
1272
1273                 g_free (text);
1274                 g_object_unref (contact);
1275         }
1276 }
1277
1278 GtkWidget *
1279 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1280 {
1281         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1282         EmpathyContact             *contact;
1283         GtkWidget                  *menu;
1284         GtkWidget                  *item;
1285         GtkWidget                  *image;
1286
1287         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1288
1289         contact = empathy_contact_list_view_get_selected (view);
1290         if (!contact) {
1291                 return NULL;
1292         }
1293
1294         menu = empathy_contact_menu_new (contact, priv->contact_features);
1295
1296         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1297                 g_object_unref (contact);
1298                 return menu;
1299         }
1300
1301         if (menu) {
1302                 /* Separator */
1303                 item = gtk_separator_menu_item_new ();
1304                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1305                 gtk_widget_show (item);
1306         } else {
1307                 menu = gtk_menu_new ();
1308         }
1309
1310         /* Remove contact */
1311         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1312                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1313                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1314                                                       GTK_ICON_SIZE_MENU);
1315                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1316                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1317                 gtk_widget_show (item);
1318                 g_signal_connect (item, "activate",
1319                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1320                                   view);
1321         }
1322
1323         g_object_unref (contact);
1324
1325         return menu;
1326 }
1327