]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Show an audio and a video call button
[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., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  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_start_voip_call (EmpathyCellRendererActivatable *cell,
619     const gchar                    *path_string,
620     EmpathyContactListView         *view,
621     gboolean with_video)
622 {
623         EmpathyContactListViewPriv *priv = GET_PRIV (view);
624         GtkTreeModel               *model;
625         GtkTreeIter                 iter;
626         EmpathyContact             *contact;
627
628         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
629                 return;
630         }
631
632         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
633         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
634                 return;
635         }
636
637         gtk_tree_model_get (model, &iter,
638                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
639                             -1);
640
641         if (contact) {
642                 EmpathyCallFactory *factory;
643                 factory = empathy_call_factory_get ();
644                 empathy_call_factory_new_call_with_streams (factory, contact,
645                         TRUE, with_video);
646                 g_object_unref (contact);
647         }
648 }
649
650 static void
651 contact_list_view_video_call_activated_cb (
652     EmpathyCellRendererActivatable *cell,
653     const gchar                    *path_string,
654     EmpathyContactListView         *view)
655 {
656   contact_list_start_voip_call (cell, path_string, view, TRUE);
657 }
658
659
660 static void
661 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
662                                      const gchar                    *path_string,
663                                      EmpathyContactListView         *view)
664 {
665   contact_list_start_voip_call (cell, path_string, view, FALSE);
666 }
667
668 static void
669 contact_list_view_cell_set_background (EmpathyContactListView *view,
670                                        GtkCellRenderer       *cell,
671                                        gboolean               is_group,
672                                        gboolean               is_active)
673 {
674         GdkColor  color;
675         GtkStyle *style;
676
677         style = gtk_widget_get_style (GTK_WIDGET (view));
678
679         if (!is_group && is_active) {
680                 color = style->bg[GTK_STATE_SELECTED];
681
682                 /* Here we take the current theme colour and add it to
683                  * the colour for white and average the two. This
684                  * gives a colour which is inline with the theme but
685                  * slightly whiter.
686                  */
687                 color.red = (color.red + (style->white).red) / 2;
688                 color.green = (color.green + (style->white).green) / 2;
689                 color.blue = (color.blue + (style->white).blue) / 2;
690
691                 g_object_set (cell,
692                               "cell-background-gdk", &color,
693                               NULL);
694         } else {
695                 g_object_set (cell,
696                               "cell-background-gdk", NULL,
697                               NULL);
698         }
699 }
700
701 static void
702 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
703                                          GtkCellRenderer       *cell,
704                                          GtkTreeModel          *model,
705                                          GtkTreeIter           *iter,
706                                          EmpathyContactListView *view)
707 {
708         gchar    *icon_name;
709         gboolean  is_group;
710         gboolean  is_active;
711
712         gtk_tree_model_get (model, iter,
713                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
714                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
715                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
716                             -1);
717
718         g_object_set (cell,
719                       "visible", !is_group,
720                       "icon-name", icon_name,
721                       NULL);
722
723         g_free (icon_name);
724
725         contact_list_view_cell_set_background (view, cell, is_group, is_active);
726 }
727
728 static void
729 contact_list_view_audio_call_cell_data_func (
730                                        GtkTreeViewColumn      *tree_column,
731                                        GtkCellRenderer        *cell,
732                                        GtkTreeModel           *model,
733                                        GtkTreeIter            *iter,
734                                        EmpathyContactListView *view)
735 {
736         gboolean is_group;
737         gboolean is_active;
738         gboolean can_voip;
739
740         gtk_tree_model_get (model, iter,
741                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
742                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
743                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
744                             -1);
745
746         g_object_set (cell,
747                       "visible", !is_group && can_voip,
748                       "icon-name", EMPATHY_IMAGE_VOIP,
749                       NULL);
750
751         contact_list_view_cell_set_background (view, cell, is_group, is_active);
752 }
753
754 static void
755 contact_list_view_video_call_cell_data_func (
756                                        GtkTreeViewColumn      *tree_column,
757                                        GtkCellRenderer        *cell,
758                                        GtkTreeModel           *model,
759                                        GtkTreeIter            *iter,
760                                        EmpathyContactListView *view)
761 {
762         gboolean is_group;
763         gboolean is_active;
764         gboolean can_voip;
765
766         gtk_tree_model_get (model, iter,
767                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
768                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
769                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
770                             -1);
771
772         g_object_set (cell,
773                       "visible", !is_group && can_voip,
774                       "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
775                       NULL);
776
777         contact_list_view_cell_set_background (view, cell, is_group, is_active);
778 }
779
780
781 static void
782 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
783                                          GtkCellRenderer       *cell,
784                                          GtkTreeModel          *model,
785                                          GtkTreeIter           *iter,
786                                          EmpathyContactListView *view)
787 {
788         GdkPixbuf *pixbuf;
789         gboolean   show_avatar;
790         gboolean   is_group;
791         gboolean   is_active;
792
793         gtk_tree_model_get (model, iter,
794                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
795                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
796                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
797                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
798                             -1);
799
800         g_object_set (cell,
801                       "visible", !is_group && show_avatar,
802                       "pixbuf", pixbuf,
803                       NULL);
804
805         if (pixbuf) {
806                 g_object_unref (pixbuf);
807         }
808
809         contact_list_view_cell_set_background (view, cell, is_group, is_active);
810 }
811
812 static void
813 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
814                                        GtkCellRenderer       *cell,
815                                        GtkTreeModel          *model,
816                                        GtkTreeIter           *iter,
817                                        EmpathyContactListView *view)
818 {
819         gboolean is_group;
820         gboolean is_active;
821         gboolean show_status;
822
823         gtk_tree_model_get (model, iter,
824                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
825                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
826                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
827                             -1);
828
829         g_object_set (cell,
830                       "show-status", show_status,
831                       NULL);
832
833         contact_list_view_cell_set_background (view, cell, is_group, is_active);
834 }
835
836 static void
837 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
838                                            GtkCellRenderer       *cell,
839                                            GtkTreeModel          *model,
840                                            GtkTreeIter           *iter,
841                                            EmpathyContactListView *view)
842 {
843         gboolean is_group;
844         gboolean is_active;
845
846         gtk_tree_model_get (model, iter,
847                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
848                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
849                             -1);
850
851         if (gtk_tree_model_iter_has_child (model, iter)) {
852                 GtkTreePath *path;
853                 gboolean     row_expanded;
854
855                 path = gtk_tree_model_get_path (model, iter);
856                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
857                 gtk_tree_path_free (path);
858
859                 g_object_set (cell,
860                               "visible", TRUE,
861                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
862                               NULL);
863         } else {
864                 g_object_set (cell, "visible", FALSE, NULL);
865         }
866
867         contact_list_view_cell_set_background (view, cell, is_group, is_active);
868 }
869
870 static void
871 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
872                                              GtkTreeIter           *iter,
873                                              GtkTreePath           *path,
874                                              gpointer               user_data)
875 {
876         EmpathyContactListViewPriv *priv = GET_PRIV (view);
877         GtkTreeModel               *model;
878         gchar                      *name;
879         gboolean                    expanded;
880
881         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
882                 return;
883         }
884
885         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
886
887         gtk_tree_model_get (model, iter,
888                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
889                             -1);
890
891         expanded = GPOINTER_TO_INT (user_data);
892         empathy_contact_group_set_expanded (name, expanded);
893
894         g_free (name);
895 }
896
897 static void
898 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
899                                             GtkTreePath           *path,
900                                             GtkTreeIter           *iter,
901                                             EmpathyContactListView *view)
902 {
903         EmpathyContactListViewPriv *priv = GET_PRIV (view);
904         gboolean  is_group = FALSE;
905         gchar    *name = NULL;
906
907         gtk_tree_model_get (model, iter,
908                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
909                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
910                             -1);
911
912         if (!is_group || EMP_STR_EMPTY (name)) {
913                 g_free (name);
914                 return;
915         }
916
917         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
918             empathy_contact_group_get_expanded (name)) {
919                 g_signal_handlers_block_by_func (view,
920                                                  contact_list_view_row_expand_or_collapse_cb,
921                                                  GINT_TO_POINTER (TRUE));
922                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
923                 g_signal_handlers_unblock_by_func (view,
924                                                    contact_list_view_row_expand_or_collapse_cb,
925                                                    GINT_TO_POINTER (TRUE));
926         } else {
927                 g_signal_handlers_block_by_func (view,
928                                                  contact_list_view_row_expand_or_collapse_cb,
929                                                  GINT_TO_POINTER (FALSE));
930                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
931                 g_signal_handlers_unblock_by_func (view,
932                                                    contact_list_view_row_expand_or_collapse_cb,
933                                                    GINT_TO_POINTER (FALSE));
934         }
935
936         g_free (name);
937 }
938
939 static void
940 contact_list_view_setup (EmpathyContactListView *view)
941 {
942         EmpathyContactListViewPriv *priv;
943         GtkCellRenderer           *cell;
944         GtkTreeViewColumn         *col;
945         gint                       i;
946
947         priv = GET_PRIV (view);
948
949         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
950                                              empathy_contact_list_store_search_equal_func,
951                                              NULL, NULL);
952
953         g_signal_connect (priv->store, "row-has-child-toggled",
954                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
955                           view);
956         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
957                                  GTK_TREE_MODEL (priv->store));
958
959         /* Setup view */
960         g_object_set (view,
961                       "headers-visible", FALSE,
962                       "reorderable", TRUE,
963                       "show-expanders", FALSE,
964                       NULL);
965
966         col = gtk_tree_view_column_new ();
967
968         /* State */
969         cell = gtk_cell_renderer_pixbuf_new ();
970         gtk_tree_view_column_pack_start (col, cell, FALSE);
971         gtk_tree_view_column_set_cell_data_func (
972                 col, cell,
973                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
974                 view, NULL);
975
976         g_object_set (cell,
977                       "xpad", 5,
978                       "ypad", 1,
979                       "visible", FALSE,
980                       NULL);
981
982         /* Name */
983         cell = empathy_cell_renderer_text_new ();
984         gtk_tree_view_column_pack_start (col, cell, TRUE);
985         gtk_tree_view_column_set_cell_data_func (
986                 col, cell,
987                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
988                 view, NULL);
989
990         gtk_tree_view_column_add_attribute (col, cell,
991                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
992         gtk_tree_view_column_add_attribute (col, cell,
993                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
994         gtk_tree_view_column_add_attribute (col, cell,
995                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
996
997         /* Audio Call Icon */
998         cell = empathy_cell_renderer_activatable_new ();
999         gtk_tree_view_column_pack_start (col, cell, FALSE);
1000         gtk_tree_view_column_set_cell_data_func (
1001                 col, cell,
1002                 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1003                 view, NULL);
1004
1005         g_object_set (cell,
1006                       "visible", FALSE,
1007                       NULL);
1008
1009         g_signal_connect (cell, "path-activated",
1010                           G_CALLBACK (contact_list_view_audio_call_activated_cb),
1011                           view);
1012
1013         /* Video Call Icon */
1014         cell = empathy_cell_renderer_activatable_new ();
1015         gtk_tree_view_column_pack_start (col, cell, FALSE);
1016         gtk_tree_view_column_set_cell_data_func (
1017                 col, cell,
1018                 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1019                 view, NULL);
1020
1021         g_object_set (cell,
1022                       "visible", FALSE,
1023                       NULL);
1024
1025         g_signal_connect (cell, "path-activated",
1026                           G_CALLBACK (contact_list_view_video_call_activated_cb),
1027                           view);
1028
1029         /* Avatar */
1030         cell = gtk_cell_renderer_pixbuf_new ();
1031         gtk_tree_view_column_pack_start (col, cell, FALSE);
1032         gtk_tree_view_column_set_cell_data_func (
1033                 col, cell,
1034                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1035                 view, NULL);
1036
1037         g_object_set (cell,
1038                       "xpad", 0,
1039                       "ypad", 0,
1040                       "visible", FALSE,
1041                       "width", 32,
1042                       "height", 32,
1043                       NULL);
1044
1045         /* Expander */
1046         cell = empathy_cell_renderer_expander_new ();
1047         gtk_tree_view_column_pack_end (col, cell, FALSE);
1048         gtk_tree_view_column_set_cell_data_func (
1049                 col, cell,
1050                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1051                 view, NULL);
1052
1053         /* Actually add the column now we have added all cell renderers */
1054         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1055
1056         /* Drag & Drop. */
1057         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1058                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1059                                                       FALSE);
1060         }
1061
1062         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1063                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1064                                                         FALSE);
1065         }
1066 }
1067
1068 static void
1069 contact_list_view_set_list_features (EmpathyContactListView         *view,
1070                                      EmpathyContactListFeatureFlags  features)
1071 {
1072         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1073         gboolean                    has_tooltip;
1074
1075         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1076
1077         priv->list_features = features;
1078
1079         /* Update DnD source/dest */
1080         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1081                 gtk_drag_source_set (GTK_WIDGET (view),
1082                                      GDK_BUTTON1_MASK,
1083                                      drag_types_source,
1084                                      G_N_ELEMENTS (drag_types_source),
1085                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1086         } else {
1087                 gtk_drag_source_unset (GTK_WIDGET (view));
1088
1089         }
1090
1091         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1092                 gtk_drag_dest_set (GTK_WIDGET (view),
1093                                    GTK_DEST_DEFAULT_ALL,
1094                                    drag_types_dest,
1095                                    G_N_ELEMENTS (drag_types_dest),
1096                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1097         } else {
1098                 /* FIXME: URI could still be droped depending on FT feature */
1099                 gtk_drag_dest_unset (GTK_WIDGET (view));
1100         }
1101
1102         /* Update has-tooltip */
1103         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1104         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1105 }
1106
1107 static void
1108 contact_list_view_finalize (GObject *object)
1109 {
1110         EmpathyContactListViewPriv *priv;
1111
1112         priv = GET_PRIV (object);
1113
1114         if (priv->store) {
1115                 g_object_unref (priv->store);
1116         }
1117         if (priv->tooltip_widget) {
1118                 gtk_widget_destroy (priv->tooltip_widget);
1119         }
1120
1121         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1122 }
1123
1124 static void
1125 contact_list_view_get_property (GObject    *object,
1126                                 guint       param_id,
1127                                 GValue     *value,
1128                                 GParamSpec *pspec)
1129 {
1130         EmpathyContactListViewPriv *priv;
1131
1132         priv = GET_PRIV (object);
1133
1134         switch (param_id) {
1135         case PROP_STORE:
1136                 g_value_set_object (value, priv->store);
1137                 break;
1138         case PROP_LIST_FEATURES:
1139                 g_value_set_flags (value, priv->list_features);
1140                 break;
1141         case PROP_CONTACT_FEATURES:
1142                 g_value_set_flags (value, priv->contact_features);
1143                 break;
1144         default:
1145                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1146                 break;
1147         };
1148 }
1149
1150 static void
1151 contact_list_view_set_property (GObject      *object,
1152                                 guint         param_id,
1153                                 const GValue *value,
1154                                 GParamSpec   *pspec)
1155 {
1156         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1157         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1158
1159         switch (param_id) {
1160         case PROP_STORE:
1161                 priv->store = g_value_dup_object (value);
1162                 contact_list_view_setup (view);
1163                 break;
1164         case PROP_LIST_FEATURES:
1165                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1166                 break;
1167         case PROP_CONTACT_FEATURES:
1168                 priv->contact_features = g_value_get_flags (value);
1169                 break;
1170         default:
1171                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1172                 break;
1173         };
1174 }
1175
1176 static void
1177 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1178 {
1179         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1180         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1181         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1182
1183         object_class->finalize = contact_list_view_finalize;
1184         object_class->get_property = contact_list_view_get_property;
1185         object_class->set_property = contact_list_view_set_property;
1186
1187         widget_class->drag_data_received = contact_list_view_drag_data_received;
1188         widget_class->drag_drop          = contact_list_view_drag_drop;
1189         widget_class->drag_begin         = contact_list_view_drag_begin;
1190         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1191         widget_class->drag_end           = contact_list_view_drag_end;
1192         widget_class->drag_motion        = contact_list_view_drag_motion;
1193
1194         /* We use the class method to let user of this widget to connect to
1195          * the signal and stop emission of the signal so the default handler
1196          * won't be called. */
1197         tree_view_class->row_activated = contact_list_view_row_activated;
1198
1199         signals[DRAG_CONTACT_RECEIVED] =
1200                 g_signal_new ("drag-contact-received",
1201                               G_OBJECT_CLASS_TYPE (klass),
1202                               G_SIGNAL_RUN_LAST,
1203                               0,
1204                               NULL, NULL,
1205                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1206                               G_TYPE_NONE,
1207                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1208
1209         g_object_class_install_property (object_class,
1210                                          PROP_STORE,
1211                                          g_param_spec_object ("store",
1212                                                              "The store of the view",
1213                                                              "The store of the view",
1214                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1215                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1216         g_object_class_install_property (object_class,
1217                                          PROP_LIST_FEATURES,
1218                                          g_param_spec_flags ("list-features",
1219                                                              "Features of the view",
1220                                                              "Falgs for all enabled features",
1221                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1222                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1223                                                               G_PARAM_READWRITE));
1224         g_object_class_install_property (object_class,
1225                                          PROP_CONTACT_FEATURES,
1226                                          g_param_spec_flags ("contact-features",
1227                                                              "Features of the contact menu",
1228                                                              "Falgs for all enabled features for the menu",
1229                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1230                                                               EMPATHY_CONTACT_FEATURE_NONE,
1231                                                               G_PARAM_READWRITE));
1232
1233         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1234 }
1235
1236 static void
1237 empathy_contact_list_view_init (EmpathyContactListView *view)
1238 {
1239         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1240                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1241
1242         view->priv = priv;
1243         /* Get saved group states. */
1244         empathy_contact_groups_get_all ();
1245
1246         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1247                                               empathy_contact_list_store_row_separator_func,
1248                                               NULL, NULL);
1249
1250         /* Connect to tree view signals rather than override. */
1251         g_signal_connect (view, "button-press-event",
1252                           G_CALLBACK (contact_list_view_button_press_event_cb),
1253                           NULL);
1254         g_signal_connect (view, "key-press-event",
1255                           G_CALLBACK (contact_list_view_key_press_event_cb),
1256                           NULL);
1257         g_signal_connect (view, "row-expanded",
1258                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1259                           GINT_TO_POINTER (TRUE));
1260         g_signal_connect (view, "row-collapsed",
1261                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1262                           GINT_TO_POINTER (FALSE));
1263         g_signal_connect (view, "query-tooltip",
1264                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1265                           NULL);
1266 }
1267
1268 EmpathyContactListView *
1269 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1270                                EmpathyContactListFeatureFlags  list_features,
1271                                EmpathyContactFeatureFlags      contact_features)
1272 {
1273         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1274         
1275         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1276                              "store", store,
1277                              "contact-features", contact_features,
1278                              "list-features", list_features,
1279                              NULL);
1280 }
1281
1282 EmpathyContact *
1283 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1284 {
1285         EmpathyContactListViewPriv *priv;
1286         GtkTreeSelection          *selection;
1287         GtkTreeIter                iter;
1288         GtkTreeModel              *model;
1289         EmpathyContact             *contact;
1290
1291         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1292
1293         priv = GET_PRIV (view);
1294
1295         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1296         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1297                 return NULL;
1298         }
1299
1300         gtk_tree_model_get (model, &iter,
1301                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1302                             -1);
1303
1304         return contact;
1305 }
1306
1307 gchar *
1308 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1309 {
1310         EmpathyContactListViewPriv *priv;
1311         GtkTreeSelection          *selection;
1312         GtkTreeIter                iter;
1313         GtkTreeModel              *model;
1314         gboolean                   is_group;
1315         gchar                     *name;
1316
1317         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1318
1319         priv = GET_PRIV (view);
1320
1321         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1322         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1323                 return NULL;
1324         }
1325
1326         gtk_tree_model_get (model, &iter,
1327                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1328                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1329                             -1);
1330
1331         if (!is_group) {
1332                 g_free (name);
1333                 return NULL;
1334         }
1335
1336         return name;
1337 }
1338
1339 static gboolean
1340 contact_list_view_remove_dialog_show (GtkWindow   *parent,
1341                                       const gchar *message,
1342                                       const gchar *secondary_text)
1343 {
1344         GtkWidget *dialog;
1345         gboolean res;
1346         
1347         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1348                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1349                                          "%s", message);
1350         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1351                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1352                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1353                                 NULL);
1354         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1355                                                   "%s", secondary_text);
1356
1357         gtk_widget_show (dialog);
1358
1359         res = gtk_dialog_run (GTK_DIALOG (dialog));
1360         gtk_widget_destroy (dialog);
1361
1362         return (res == GTK_RESPONSE_YES);
1363 }
1364
1365 static void
1366 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1367                                             EmpathyContactListView *view)
1368 {
1369         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1370         gchar                      *group;
1371
1372         group = empathy_contact_list_view_get_selected_group (view);
1373         if (group) {
1374                 gchar     *text;
1375                 GtkWindow *parent;
1376
1377                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1378                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1379                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1380                         EmpathyContactList *list;
1381
1382                         list = empathy_contact_list_store_get_list_iface (priv->store);
1383                         empathy_contact_list_remove_group (list, group);
1384                 }
1385
1386                 g_free (text);
1387         }
1388
1389         g_free (group);
1390 }
1391
1392 GtkWidget *
1393 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1394 {
1395         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1396         gchar                      *group;
1397         GtkWidget                  *menu;
1398         GtkWidget                  *item;
1399         GtkWidget                  *image;
1400
1401         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1402
1403         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1404                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1405                 return NULL;
1406         }
1407
1408         group = empathy_contact_list_view_get_selected_group (view);
1409         if (!group) {
1410                 return NULL;
1411         }
1412
1413         menu = gtk_menu_new ();
1414
1415         /* FIXME: Not implemented yet
1416         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1417                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1418                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1419                 gtk_widget_show (item);
1420                 g_signal_connect (item, "activate",
1421                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1422                                   view);
1423         }*/
1424
1425         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1426                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1427                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1428                                                       GTK_ICON_SIZE_MENU);
1429                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1430                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1431                 gtk_widget_show (item);
1432                 g_signal_connect (item, "activate",
1433                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1434                                   view);
1435         }
1436
1437         g_free (group);
1438
1439         return menu;
1440 }
1441
1442 static void
1443 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1444                                       EmpathyContactListView *view)
1445 {
1446         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1447         EmpathyContact             *contact;
1448                 
1449         contact = empathy_contact_list_view_dup_selected (view);
1450
1451         if (contact) {
1452                 gchar     *text;
1453                 GtkWindow *parent;
1454
1455                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1456                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1457                                         empathy_contact_get_name (contact));                                            
1458                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1459                         EmpathyContactList *list;
1460
1461                         list = empathy_contact_list_store_get_list_iface (priv->store);
1462                         empathy_contact_list_remove (list, contact,
1463                                 _("Sorry, I don't want you in my contact list anymore."));
1464                 }
1465
1466                 g_free (text);
1467                 g_object_unref (contact);
1468         }
1469 }
1470
1471 GtkWidget *
1472 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1473 {
1474         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1475         EmpathyContact             *contact;
1476         GtkWidget                  *menu;
1477         GtkWidget                  *item;
1478         GtkWidget                  *image;
1479
1480         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1481
1482         contact = empathy_contact_list_view_dup_selected (view);
1483         if (!contact) {
1484                 return NULL;
1485         }
1486
1487         menu = empathy_contact_menu_new (contact, priv->contact_features);
1488
1489         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1490                 g_object_unref (contact);
1491                 return menu;
1492         }
1493
1494         if (menu) {
1495                 /* Separator */
1496                 item = gtk_separator_menu_item_new ();
1497                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1498                 gtk_widget_show (item);
1499         } else {
1500                 menu = gtk_menu_new ();
1501         }
1502
1503         /* Remove contact */
1504         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1505                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1506                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1507                                                       GTK_ICON_SIZE_MENU);
1508                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1509                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1510                 gtk_widget_show (item);
1511                 g_signal_connect (item, "activate",
1512                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1513                                   view);
1514         }
1515
1516         g_object_unref (contact);
1517
1518         return menu;
1519 }
1520