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