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