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