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