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