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