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