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