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