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