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