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