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