]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Implemented file drags to contact list, along with row highlights
[empathy.git] / libempathy-gtk / empathy-contact-list-view.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2005-2007 Imendio AB
4  * Copyright (C) 2007-2008 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  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-lib.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <gtk/gtk.h>
33
34 #include <telepathy-glib/account-manager.h>
35 #include <telepathy-glib/util.h>
36
37 #include <libempathy/empathy-call-factory.h>
38 #include <libempathy/empathy-tp-contact-factory.h>
39 #include <libempathy/empathy-contact-list.h>
40 #include <libempathy/empathy-contact-groups.h>
41 #include <libempathy/empathy-dispatcher.h>
42 #include <libempathy/empathy-utils.h>
43
44 #include "empathy-contact-list-view.h"
45 #include "empathy-contact-list-store.h"
46 #include "empathy-images.h"
47 #include "empathy-cell-renderer-expander.h"
48 #include "empathy-cell-renderer-text.h"
49 #include "empathy-cell-renderer-activatable.h"
50 #include "empathy-ui-utils.h"
51 #include "empathy-gtk-enum-types.h"
52 #include "empathy-gtk-marshal.h"
53
54 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
55 #include <libempathy/empathy-debug.h>
56
57 /* Active users are those which have recently changed state
58  * (e.g. online, offline or from normal to a busy state).
59  */
60
61 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyContactListView)
62 typedef struct {
63         EmpathyContactListStore        *store;
64         GtkTreeRowReference            *drag_row;
65         EmpathyContactListFeatureFlags  list_features;
66         EmpathyContactFeatureFlags      contact_features;
67         GtkWidget                      *tooltip_widget;
68 } EmpathyContactListViewPriv;
69
70 typedef struct {
71         EmpathyContactListView *view;
72         GtkTreePath           *path;
73         guint                  timeout_id;
74 } DragMotionData;
75
76 typedef struct {
77         EmpathyContactListView *view;
78         EmpathyContact         *contact;
79         gboolean               remove;
80 } ShowActiveData;
81
82 enum {
83         PROP_0,
84         PROP_STORE,
85         PROP_LIST_FEATURES,
86         PROP_CONTACT_FEATURES,
87 };
88
89 enum DndDragType {
90         DND_DRAG_TYPE_CONTACT_ID,
91         DND_DRAG_TYPE_URI_LIST,
92         DND_DRAG_TYPE_STRING,
93 };
94
95 static const GtkTargetEntry drag_types_dest[] = {
96         { "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
97         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
98         { "text/plain",      0, DND_DRAG_TYPE_STRING },
99         { "STRING",          0, DND_DRAG_TYPE_STRING },
100 };
101
102 static const GtkTargetEntry drag_types_dest_file[] = {
103         { "text/uri-list",   0, DND_DRAG_TYPE_URI_LIST },
104 };
105
106 static const GtkTargetEntry drag_types_source[] = {
107         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
108 };
109
110 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
111 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
112
113 enum {
114         DRAG_CONTACT_RECEIVED,
115         LAST_SIGNAL
116 };
117
118 static guint signals[LAST_SIGNAL];
119
120 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
121
122 static void
123 contact_list_view_tooltip_destroy_cb (GtkWidget              *widget,
124                                       EmpathyContactListView *view)
125 {
126         EmpathyContactListViewPriv *priv = GET_PRIV (view);
127
128         if (priv->tooltip_widget) {
129                 DEBUG ("Tooltip destroyed");
130                 g_object_unref (priv->tooltip_widget);
131                 priv->tooltip_widget = NULL;
132         }
133 }
134
135 static gboolean
136 contact_list_view_query_tooltip_cb (EmpathyContactListView *view,
137                                     gint                    x,
138                                     gint                    y,
139                                     gboolean                keyboard_mode,
140                                     GtkTooltip             *tooltip,
141                                     gpointer                user_data)
142 {
143         EmpathyContactListViewPriv *priv = GET_PRIV (view);
144         EmpathyContact             *contact;
145         GtkTreeModel               *model;
146         GtkTreeIter                 iter;
147         GtkTreePath                *path;
148         static gint                 running = 0;
149         gboolean                    ret = FALSE;
150
151         /* Avoid an infinite loop. See GNOME bug #574377 */
152         if (running > 0) {
153                 return FALSE;
154         }
155         running++;
156
157         if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
158                                                 keyboard_mode,
159                                                 &model, &path, &iter)) {
160                 goto OUT;
161         }
162
163         gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
164         gtk_tree_path_free (path);
165
166         gtk_tree_model_get (model, &iter,
167                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
168                             -1);
169         if (!contact) {
170                 goto OUT;
171         }
172
173         if (!priv->tooltip_widget) {
174                 priv->tooltip_widget = empathy_contact_widget_new (contact,
175                         EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
176                         EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
177                 g_object_ref (priv->tooltip_widget);
178                 g_signal_connect (priv->tooltip_widget, "destroy",
179                                   G_CALLBACK (contact_list_view_tooltip_destroy_cb),
180                                   view);
181                 gtk_widget_show (priv->tooltip_widget);
182         } else {
183                 empathy_contact_widget_set_contact (priv->tooltip_widget,
184                                                     contact);
185         }
186
187         gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
188         ret = TRUE;
189
190         g_object_unref (contact);
191 OUT:
192         running--;
193
194         return ret;
195 }
196
197 typedef struct {
198         gchar *new_group;
199         gchar *old_group;
200         GdkDragAction action;
201 } DndGetContactData;
202
203 static void
204 contact_list_view_dnd_get_contact_free (DndGetContactData *data)
205 {
206         g_free (data->new_group);
207         g_free (data->old_group);
208         g_slice_free (DndGetContactData, data);
209 }
210
211 static void
212 contact_list_view_drag_got_contact (EmpathyTpContactFactory *factory,
213                                     EmpathyContact          *contact,
214                                     const GError            *error,
215                                     gpointer                 user_data,
216                                     GObject                 *view)
217 {
218         EmpathyContactListViewPriv *priv = GET_PRIV (view);
219         DndGetContactData          *data = user_data;
220         EmpathyContactList         *list;
221
222         if (error != NULL) {
223                 DEBUG ("Error: %s", error->message);
224                 return;
225         }
226
227         DEBUG ("contact %s (%d) dragged from '%s' to '%s'",
228                 empathy_contact_get_id (contact),
229                 empathy_contact_get_handle (contact),
230                 data->old_group, data->new_group);
231
232         list = empathy_contact_list_store_get_list_iface (priv->store);
233         if (data->new_group) {
234                 empathy_contact_list_add_to_group (list, contact, data->new_group);
235         }
236         if (data->old_group && data->action == GDK_ACTION_MOVE) {
237                 empathy_contact_list_remove_from_group (list, contact, data->old_group);
238         }
239 }
240
241 static void
242 contact_list_view_drag_data_received (GtkWidget         *view,
243                                       GdkDragContext    *context,
244                                       gint               x,
245                                       gint               y,
246                                       GtkSelectionData  *selection,
247                                       guint              info,
248                                       guint              time_)
249 {
250         EmpathyContactListViewPriv *priv;
251         GtkTreeModel               *model;
252         gboolean                    success = TRUE;
253         const gchar                *sel_data;
254         gchar                     **strv = NULL;
255         gboolean                    is_row;
256         GtkTreeViewDropPosition     position;
257         GtkTreePath                *path;
258
259         priv = GET_PRIV (view);
260         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
261
262         sel_data = (const gchar*) gtk_selection_data_get_data (selection);
263         DEBUG ("Received %s%s drag & drop contact from roster with id:'%s'",
264                context->action == GDK_ACTION_MOVE ? "move" : "",
265                context->action == GDK_ACTION_COPY ? "copy" : "",
266                sel_data);
267
268         /* Get destination group information. */
269         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
270                                                     x,
271                                                     y,
272                                                     &path,
273                                                     &position);
274         if (!is_row) {
275                 success = FALSE;
276                 goto OUT;
277         }
278
279         if (info == DND_DRAG_TYPE_CONTACT_ID || info == DND_DRAG_TYPE_STRING) {
280                 TpAccountManager           *account_manager;
281                 EmpathyTpContactFactory    *factory = NULL;
282                 TpAccount                  *account = NULL;
283                 const gchar                *account_id = NULL;
284                 const gchar                *contact_id = NULL;
285                 gchar                      *new_group = NULL;
286                 gchar                      *old_group = NULL;
287                 DndGetContactData          *data;
288                 new_group = empathy_contact_list_store_get_parent_group (model,
289                                                                          path, NULL);
290                 gtk_tree_path_free (path);
291                 path = NULL;
292
293                 /* Get source group information. */
294                 if (priv->drag_row) {
295                         path = gtk_tree_row_reference_get_path (priv->drag_row);
296                         if (path) {
297                                 old_group = empathy_contact_list_store_get_parent_group (
298                                                                                          model, path, NULL);
299                                 gtk_tree_path_free (path);
300                                 path = NULL;
301                         }
302                 }
303
304                 if (!tp_strdiff (old_group, new_group)) {
305                         g_free (new_group);
306                         g_free (old_group);
307                         goto OUT;
308                 }
309
310                 /* FIXME: should probably make sure the account manager is prepared
311                  * before calling _ensure_account on it. See bug 600115. */
312                 account_manager = tp_account_manager_dup ();
313                 strv = g_strsplit (sel_data, ":", 2);
314                 if (g_strv_length (strv) == 2) {
315                         account_id = strv[0];
316                         contact_id = strv[1];
317                         account = tp_account_manager_ensure_account (account_manager, account_id);
318                 }
319                 if (account) {
320                         TpConnection *connection;
321
322                         connection = tp_account_get_connection (account);
323                         if (connection) {
324                                 factory = empathy_tp_contact_factory_dup_singleton (connection);
325                         }
326                         g_object_unref (account_manager);
327
328                         if (!factory) {
329                                 DEBUG ("Failed to get factory for account '%s'", account_id);
330                                 success = FALSE;
331                                 g_free (new_group);
332                                 g_free (old_group);
333                                 goto OUT;
334                         }
335                 }
336
337                 data = g_slice_new0 (DndGetContactData);
338                 data->new_group = new_group;
339                 data->old_group = old_group;
340                 data->action = context->action;
341
342                 /* FIXME: We should probably wait for the cb before calling
343                  * gtk_drag_finish */
344                 empathy_tp_contact_factory_get_from_id (factory, contact_id,
345                                                         contact_list_view_drag_got_contact,
346                                                         data, (GDestroyNotify) contact_list_view_dnd_get_contact_free,
347                                                         G_OBJECT (view));
348
349                 g_object_unref (factory);
350         }
351         else if (info == DND_DRAG_TYPE_URI_LIST) {
352                 GtkTreeIter iter;
353                 const gchar  *nl;
354                 gchar        *uri;
355                 GFile        *file;
356                 EmpathyContact *contact;
357
358                 gtk_tree_model_get_iter (model, &iter, path);
359                 gtk_tree_model_get (model, &iter,
360                                     EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
361                                     -1);
362                 if (!contact) {
363                         success = FALSE;
364                         goto OUT;
365                 }
366
367                 nl = strstr (sel_data, "\r\n");
368                 if (!nl) {
369                         nl = strchr (sel_data, '\n');
370                 }
371                 if (nl) {
372                         uri = g_strndup (sel_data, nl - sel_data);
373                         file = g_file_new_for_uri (uri);
374                         g_free (uri);
375                 }
376                 else {
377                         file = g_file_new_for_uri (sel_data);
378                 }
379
380                 empathy_send_file (contact, file);
381
382                 g_object_unref (file);
383                 gtk_drag_finish (context, TRUE, FALSE, time_);
384         }
385
386 OUT:
387         gtk_tree_path_free (path);
388         g_strfreev (strv);
389         gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
390 }
391
392 static gboolean
393 contact_list_view_drag_motion_cb (DragMotionData *data)
394 {
395         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
396                                   data->path,
397                                   FALSE);
398
399         data->timeout_id = 0;
400
401         return FALSE;
402 }
403
404 static gboolean
405 contact_list_view_drag_motion (GtkWidget      *widget,
406                                GdkDragContext *context,
407                                gint            x,
408                                gint            y,
409                                guint           time_)
410 {
411         EmpathyContactListViewPriv *priv;
412         GtkTreeModel               *model;
413         static GtkTargetList  *file_targets = NULL;
414         GdkAtom                target;
415         GtkTreeIter            iter;
416         static DragMotionData *dm = NULL;
417         GtkTreePath           *path;
418         gboolean               is_row;
419         gboolean               is_different = FALSE;
420         gboolean               cleanup = TRUE;
421         gboolean               retval = TRUE;
422
423         priv = GET_PRIV (EMPATHY_CONTACT_LIST_VIEW (widget));
424         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
425
426         if (file_targets == NULL) {
427                 file_targets = gtk_target_list_new (drag_types_dest_file,
428                                                     G_N_ELEMENTS (drag_types_dest_file));
429         }
430
431         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
432                                                 x,
433                                                 y,
434                                                 &path,
435                                                 NULL,
436                                                 NULL,
437                                                 NULL);
438
439         cleanup &= (!dm);
440
441         if (is_row) {
442                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
443                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
444         } else {
445                 cleanup &= FALSE;
446         }
447
448         if (path == NULL) {
449                 gdk_drag_status (context, 0, time_);
450                 gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
451                 return FALSE;
452         }
453         target = gtk_drag_dest_find_target (widget, context, file_targets);
454         gtk_tree_model_get_iter (model, &iter, path);
455
456         if (target == GDK_NONE) {
457                 gboolean    is_group;
458                 gtk_tree_model_get (model, &iter,
459                                     EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
460                                     -1);
461                 if (is_group) {
462                         gdk_drag_status (context, GDK_ACTION_MOVE, time_);
463                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
464                                                          path,
465                                                          GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
466                 }
467                 else {
468                         gdk_drag_status (context, 0, time_);
469                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
470                         retval = FALSE;
471                 }
472         }
473         else {
474                 EmpathyContact *contact;
475                 gtk_tree_model_get (model, &iter,
476                                     EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
477                                     -1);
478                 if (contact) {
479                         gdk_drag_status (context, GDK_ACTION_COPY, time_);
480                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
481                                                          path,
482                                                          GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
483                 }
484                 else {
485                         gdk_drag_status (context, 0, time_);
486                         gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
487                         retval = FALSE;
488                 }
489         }
490
491         if (!is_different && !cleanup) {
492                 return retval;
493         }
494
495         if (dm) {
496                 gtk_tree_path_free (dm->path);
497                 if (dm->timeout_id) {
498                         g_source_remove (dm->timeout_id);
499                 }
500
501                 g_free (dm);
502
503                 dm = NULL;
504         }
505
506         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
507                 dm = g_new0 (DragMotionData, 1);
508
509                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
510                 dm->path = gtk_tree_path_copy (path);
511
512                 dm->timeout_id = g_timeout_add_seconds (1,
513                         (GSourceFunc) contact_list_view_drag_motion_cb,
514                         dm);
515         }
516
517         return retval;
518 }
519
520 static void
521 contact_list_view_drag_begin (GtkWidget      *widget,
522                               GdkDragContext *context)
523 {
524         EmpathyContactListViewPriv *priv;
525         GtkTreeSelection          *selection;
526         GtkTreeModel              *model;
527         GtkTreePath               *path;
528         GtkTreeIter                iter;
529
530         priv = GET_PRIV (widget);
531
532         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
533                                                                               context);
534
535         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
536         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
537                 return;
538         }
539
540         path = gtk_tree_model_get_path (model, &iter);
541         priv->drag_row = gtk_tree_row_reference_new (model, path);
542         gtk_tree_path_free (path);
543 }
544
545 static void
546 contact_list_view_drag_data_get (GtkWidget        *widget,
547                                  GdkDragContext   *context,
548                                  GtkSelectionData *selection,
549                                  guint             info,
550                                  guint             time_)
551 {
552         EmpathyContactListViewPriv *priv;
553         GtkTreePath                *src_path;
554         GtkTreeIter                 iter;
555         GtkTreeModel               *model;
556         EmpathyContact             *contact;
557         TpAccount                  *account;
558         const gchar                *contact_id;
559         const gchar                *account_id;
560         gchar                      *str;
561
562         priv = GET_PRIV (widget);
563
564         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
565         if (!priv->drag_row) {
566                 return;
567         }
568
569         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
570         if (!src_path) {
571                 return;
572         }
573
574         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
575                 gtk_tree_path_free (src_path);
576                 return;
577         }
578
579         gtk_tree_path_free (src_path);
580
581         contact = empathy_contact_list_view_dup_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
582         if (!contact) {
583                 return;
584         }
585
586         account = empathy_contact_get_account (contact);
587         account_id = tp_proxy_get_object_path (account);
588         contact_id = empathy_contact_get_id (contact);
589         g_object_unref (contact);
590         str = g_strconcat (account_id, ":", contact_id, NULL);
591
592         switch (info) {
593         case DND_DRAG_TYPE_CONTACT_ID:
594                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
595                                         (guchar *) str, strlen (str) + 1);
596                 break;
597         }
598
599         g_free (str);
600 }
601
602 static void
603 contact_list_view_drag_end (GtkWidget      *widget,
604                             GdkDragContext *context)
605 {
606         EmpathyContactListViewPriv *priv;
607
608         priv = GET_PRIV (widget);
609
610         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
611                                                                             context);
612
613         if (priv->drag_row) {
614                 gtk_tree_row_reference_free (priv->drag_row);
615                 priv->drag_row = NULL;
616         }
617 }
618
619 static gboolean
620 contact_list_view_drag_drop (GtkWidget      *widget,
621                              GdkDragContext *drag_context,
622                              gint            x,
623                              gint            y,
624                              guint           time_)
625 {
626         return FALSE;
627 }
628
629 typedef struct {
630         EmpathyContactListView *view;
631         guint                   button;
632         guint32                 time;
633 } MenuPopupData;
634
635 static gboolean
636 contact_list_view_popup_menu_idle_cb (gpointer user_data)
637 {
638         MenuPopupData *data = user_data;
639         GtkWidget     *menu;
640
641         menu = empathy_contact_list_view_get_contact_menu (data->view);
642         if (!menu) {
643                 menu = empathy_contact_list_view_get_group_menu (data->view);
644         }
645
646         if (menu) {
647                 gtk_widget_show (menu);
648                 gtk_menu_popup (GTK_MENU (menu),
649                                 NULL, NULL, NULL, NULL,
650                                 data->button, data->time);
651         }
652
653         g_slice_free (MenuPopupData, data);
654
655         return FALSE;
656 }
657
658 static gboolean
659 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
660                                          GdkEventButton         *event,
661                                          gpointer                user_data)
662 {
663         if (event->button == 3) {
664                 MenuPopupData *data;
665
666                 data = g_slice_new (MenuPopupData);
667                 data->view = view;
668                 data->button = event->button;
669                 data->time = event->time;
670                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
671         }
672
673         return FALSE;
674 }
675
676 static gboolean
677 contact_list_view_key_press_event_cb (EmpathyContactListView *view,
678                                       GdkEventKey            *event,
679                                       gpointer                user_data)
680 {
681         if (event->keyval == GDK_Menu) {
682                 MenuPopupData *data;
683
684                 data = g_slice_new (MenuPopupData);
685                 data->view = view;
686                 data->button = 0;
687                 data->time = event->time;
688                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
689         }
690
691         return FALSE;
692 }
693
694 static void
695 contact_list_view_row_activated (GtkTreeView       *view,
696                                  GtkTreePath       *path,
697                                  GtkTreeViewColumn *column)
698 {
699         EmpathyContactListViewPriv *priv = GET_PRIV (view);
700         EmpathyContact             *contact;
701         GtkTreeModel               *model;
702         GtkTreeIter                 iter;
703
704         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
705                 return;
706         }
707
708         model = GTK_TREE_MODEL (priv->store);
709         gtk_tree_model_get_iter (model, &iter, path);
710         gtk_tree_model_get (model, &iter,
711                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
712                             -1);
713
714         if (contact) {
715                 DEBUG ("Starting a chat");
716                 empathy_dispatcher_chat_with_contact (contact, NULL, NULL);
717                 g_object_unref (contact);
718         }
719 }
720
721 static void
722 contact_list_start_voip_call (EmpathyCellRendererActivatable *cell,
723     const gchar                    *path_string,
724     EmpathyContactListView         *view,
725     gboolean with_video)
726 {
727         EmpathyContactListViewPriv *priv = GET_PRIV (view);
728         GtkTreeModel               *model;
729         GtkTreeIter                 iter;
730         EmpathyContact             *contact;
731
732         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
733                 return;
734         }
735
736         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
737         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
738                 return;
739         }
740
741         gtk_tree_model_get (model, &iter,
742                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
743                             -1);
744
745         if (contact) {
746                 EmpathyCallFactory *factory;
747                 factory = empathy_call_factory_get ();
748                 empathy_call_factory_new_call_with_streams (factory, contact,
749                         TRUE, with_video);
750                 g_object_unref (contact);
751         }
752 }
753
754 static void
755 contact_list_view_video_call_activated_cb (
756     EmpathyCellRendererActivatable *cell,
757     const gchar                    *path_string,
758     EmpathyContactListView         *view)
759 {
760   contact_list_start_voip_call (cell, path_string, view, TRUE);
761 }
762
763
764 static void
765 contact_list_view_audio_call_activated_cb (EmpathyCellRendererActivatable *cell,
766                                      const gchar                    *path_string,
767                                      EmpathyContactListView         *view)
768 {
769   contact_list_start_voip_call (cell, path_string, view, FALSE);
770 }
771
772 static void
773 contact_list_view_cell_set_background (EmpathyContactListView *view,
774                                        GtkCellRenderer       *cell,
775                                        gboolean               is_group,
776                                        gboolean               is_active)
777 {
778         GdkColor  color;
779         GtkStyle *style;
780
781         style = gtk_widget_get_style (GTK_WIDGET (view));
782
783         if (!is_group && is_active) {
784                 color = style->bg[GTK_STATE_SELECTED];
785
786                 /* Here we take the current theme colour and add it to
787                  * the colour for white and average the two. This
788                  * gives a colour which is inline with the theme but
789                  * slightly whiter.
790                  */
791                 color.red = (color.red + (style->white).red) / 2;
792                 color.green = (color.green + (style->white).green) / 2;
793                 color.blue = (color.blue + (style->white).blue) / 2;
794
795                 g_object_set (cell,
796                               "cell-background-gdk", &color,
797                               NULL);
798         } else {
799                 g_object_set (cell,
800                               "cell-background-gdk", NULL,
801                               NULL);
802         }
803 }
804
805 static void
806 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
807                                          GtkCellRenderer       *cell,
808                                          GtkTreeModel          *model,
809                                          GtkTreeIter           *iter,
810                                          EmpathyContactListView *view)
811 {
812         gchar    *icon_name;
813         gboolean  is_group;
814         gboolean  is_active;
815
816         gtk_tree_model_get (model, iter,
817                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
818                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
819                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
820                             -1);
821
822         g_object_set (cell,
823                       "visible", !is_group,
824                       "icon-name", icon_name,
825                       NULL);
826
827         g_free (icon_name);
828
829         contact_list_view_cell_set_background (view, cell, is_group, is_active);
830 }
831
832 static void
833 contact_list_view_audio_call_cell_data_func (
834                                        GtkTreeViewColumn      *tree_column,
835                                        GtkCellRenderer        *cell,
836                                        GtkTreeModel           *model,
837                                        GtkTreeIter            *iter,
838                                        EmpathyContactListView *view)
839 {
840         gboolean is_group;
841         gboolean is_active;
842         gboolean can_voip;
843
844         gtk_tree_model_get (model, iter,
845                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
846                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
847                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
848                             -1);
849
850         g_object_set (cell,
851                       "visible", !is_group && can_voip,
852                       "icon-name", EMPATHY_IMAGE_VOIP,
853                       NULL);
854
855         contact_list_view_cell_set_background (view, cell, is_group, is_active);
856 }
857
858 static void
859 contact_list_view_video_call_cell_data_func (
860                                        GtkTreeViewColumn      *tree_column,
861                                        GtkCellRenderer        *cell,
862                                        GtkTreeModel           *model,
863                                        GtkTreeIter            *iter,
864                                        EmpathyContactListView *view)
865 {
866         gboolean is_group;
867         gboolean is_active;
868         gboolean can_voip;
869
870         gtk_tree_model_get (model, iter,
871                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
872                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
873                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
874                             -1);
875
876         g_object_set (cell,
877                       "visible", !is_group && can_voip,
878                       "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
879                       NULL);
880
881         contact_list_view_cell_set_background (view, cell, is_group, is_active);
882 }
883
884
885 static void
886 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
887                                          GtkCellRenderer       *cell,
888                                          GtkTreeModel          *model,
889                                          GtkTreeIter           *iter,
890                                          EmpathyContactListView *view)
891 {
892         GdkPixbuf *pixbuf;
893         gboolean   show_avatar;
894         gboolean   is_group;
895         gboolean   is_active;
896
897         gtk_tree_model_get (model, iter,
898                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
899                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
900                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
901                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
902                             -1);
903
904         g_object_set (cell,
905                       "visible", !is_group && show_avatar,
906                       "pixbuf", pixbuf,
907                       NULL);
908
909         if (pixbuf) {
910                 g_object_unref (pixbuf);
911         }
912
913         contact_list_view_cell_set_background (view, cell, is_group, is_active);
914 }
915
916 static void
917 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
918                                        GtkCellRenderer       *cell,
919                                        GtkTreeModel          *model,
920                                        GtkTreeIter           *iter,
921                                        EmpathyContactListView *view)
922 {
923         gboolean is_group;
924         gboolean is_active;
925         gboolean show_status;
926         gchar *name;
927
928         gtk_tree_model_get (model, iter,
929                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
930                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
931                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
932                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
933                             -1);
934
935         g_object_set (cell,
936                       "show-status", show_status,
937                       "text", name,
938                       NULL);
939         g_free (name);
940
941         contact_list_view_cell_set_background (view, cell, is_group, is_active);
942 }
943
944 static void
945 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
946                                            GtkCellRenderer       *cell,
947                                            GtkTreeModel          *model,
948                                            GtkTreeIter           *iter,
949                                            EmpathyContactListView *view)
950 {
951         gboolean is_group;
952         gboolean is_active;
953
954         gtk_tree_model_get (model, iter,
955                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
956                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
957                             -1);
958
959         if (gtk_tree_model_iter_has_child (model, iter)) {
960                 GtkTreePath *path;
961                 gboolean     row_expanded;
962
963                 path = gtk_tree_model_get_path (model, iter);
964                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
965                 gtk_tree_path_free (path);
966
967                 g_object_set (cell,
968                               "visible", TRUE,
969                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
970                               NULL);
971         } else {
972                 g_object_set (cell, "visible", FALSE, NULL);
973         }
974
975         contact_list_view_cell_set_background (view, cell, is_group, is_active);
976 }
977
978 static void
979 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
980                                              GtkTreeIter           *iter,
981                                              GtkTreePath           *path,
982                                              gpointer               user_data)
983 {
984         EmpathyContactListViewPriv *priv = GET_PRIV (view);
985         GtkTreeModel               *model;
986         gchar                      *name;
987         gboolean                    expanded;
988
989         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
990                 return;
991         }
992
993         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
994
995         gtk_tree_model_get (model, iter,
996                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
997                             -1);
998
999         expanded = GPOINTER_TO_INT (user_data);
1000         empathy_contact_group_set_expanded (name, expanded);
1001
1002         g_free (name);
1003 }
1004
1005 static void
1006 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
1007                                             GtkTreePath           *path,
1008                                             GtkTreeIter           *iter,
1009                                             EmpathyContactListView *view)
1010 {
1011         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1012         gboolean  is_group = FALSE;
1013         gchar    *name = NULL;
1014
1015         gtk_tree_model_get (model, iter,
1016                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1017                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1018                             -1);
1019
1020         if (!is_group || EMP_STR_EMPTY (name)) {
1021                 g_free (name);
1022                 return;
1023         }
1024
1025         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1026             empathy_contact_group_get_expanded (name)) {
1027                 g_signal_handlers_block_by_func (view,
1028                                                  contact_list_view_row_expand_or_collapse_cb,
1029                                                  GINT_TO_POINTER (TRUE));
1030                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1031                 g_signal_handlers_unblock_by_func (view,
1032                                                    contact_list_view_row_expand_or_collapse_cb,
1033                                                    GINT_TO_POINTER (TRUE));
1034         } else {
1035                 g_signal_handlers_block_by_func (view,
1036                                                  contact_list_view_row_expand_or_collapse_cb,
1037                                                  GINT_TO_POINTER (FALSE));
1038                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1039                 g_signal_handlers_unblock_by_func (view,
1040                                                    contact_list_view_row_expand_or_collapse_cb,
1041                                                    GINT_TO_POINTER (FALSE));
1042         }
1043
1044         g_free (name);
1045 }
1046
1047 static void
1048 contact_list_view_setup (EmpathyContactListView *view)
1049 {
1050         EmpathyContactListViewPriv *priv;
1051         GtkCellRenderer           *cell;
1052         GtkTreeViewColumn         *col;
1053         guint                      i;
1054
1055         priv = GET_PRIV (view);
1056
1057         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1058                                              empathy_contact_list_store_search_equal_func,
1059                                              NULL, NULL);
1060
1061         g_signal_connect (priv->store, "row-has-child-toggled",
1062                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1063                           view);
1064         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1065                                  GTK_TREE_MODEL (priv->store));
1066
1067         /* Setup view */
1068         g_object_set (view,
1069                       "headers-visible", FALSE,
1070                       "reorderable", FALSE,
1071                       "show-expanders", FALSE,
1072                       NULL);
1073
1074         col = gtk_tree_view_column_new ();
1075
1076         /* State */
1077         cell = gtk_cell_renderer_pixbuf_new ();
1078         gtk_tree_view_column_pack_start (col, cell, FALSE);
1079         gtk_tree_view_column_set_cell_data_func (
1080                 col, cell,
1081                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1082                 view, NULL);
1083
1084         g_object_set (cell,
1085                       "xpad", 5,
1086                       "ypad", 1,
1087                       "visible", FALSE,
1088                       NULL);
1089
1090         /* Name */
1091         cell = empathy_cell_renderer_text_new ();
1092         gtk_tree_view_column_pack_start (col, cell, TRUE);
1093         gtk_tree_view_column_set_cell_data_func (
1094                 col, cell,
1095                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1096                 view, NULL);
1097
1098         gtk_tree_view_column_add_attribute (col, cell,
1099                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1100         gtk_tree_view_column_add_attribute (col, cell,
1101                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1102         gtk_tree_view_column_add_attribute (col, cell,
1103                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1104
1105         /* Audio Call Icon */
1106         cell = empathy_cell_renderer_activatable_new ();
1107         gtk_tree_view_column_pack_start (col, cell, FALSE);
1108         gtk_tree_view_column_set_cell_data_func (
1109                 col, cell,
1110                 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1111                 view, NULL);
1112
1113         g_object_set (cell,
1114                       "visible", FALSE,
1115                       NULL);
1116
1117         g_signal_connect (cell, "path-activated",
1118                           G_CALLBACK (contact_list_view_audio_call_activated_cb),
1119                           view);
1120
1121         /* Video Call Icon */
1122         cell = empathy_cell_renderer_activatable_new ();
1123         gtk_tree_view_column_pack_start (col, cell, FALSE);
1124         gtk_tree_view_column_set_cell_data_func (
1125                 col, cell,
1126                 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1127                 view, NULL);
1128
1129         g_object_set (cell,
1130                       "visible", FALSE,
1131                       NULL);
1132
1133         g_signal_connect (cell, "path-activated",
1134                           G_CALLBACK (contact_list_view_video_call_activated_cb),
1135                           view);
1136
1137         /* Avatar */
1138         cell = gtk_cell_renderer_pixbuf_new ();
1139         gtk_tree_view_column_pack_start (col, cell, FALSE);
1140         gtk_tree_view_column_set_cell_data_func (
1141                 col, cell,
1142                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1143                 view, NULL);
1144
1145         g_object_set (cell,
1146                       "xpad", 0,
1147                       "ypad", 0,
1148                       "visible", FALSE,
1149                       "width", 32,
1150                       "height", 32,
1151                       NULL);
1152
1153         /* Expander */
1154         cell = empathy_cell_renderer_expander_new ();
1155         gtk_tree_view_column_pack_end (col, cell, FALSE);
1156         gtk_tree_view_column_set_cell_data_func (
1157                 col, cell,
1158                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1159                 view, NULL);
1160
1161         /* Actually add the column now we have added all cell renderers */
1162         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1163
1164         /* Drag & Drop. */
1165         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1166                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1167                                                       FALSE);
1168         }
1169
1170         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1171                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1172                                                         FALSE);
1173         }
1174 }
1175
1176 static void
1177 contact_list_view_set_list_features (EmpathyContactListView         *view,
1178                                      EmpathyContactListFeatureFlags  features)
1179 {
1180         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1181         gboolean                    has_tooltip;
1182
1183         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1184
1185         priv->list_features = features;
1186
1187         /* Update DnD source/dest */
1188         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1189                 gtk_drag_source_set (GTK_WIDGET (view),
1190                                      GDK_BUTTON1_MASK,
1191                                      drag_types_source,
1192                                      G_N_ELEMENTS (drag_types_source),
1193                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1194         } else {
1195                 gtk_drag_source_unset (GTK_WIDGET (view));
1196
1197         }
1198
1199         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1200                 gtk_drag_dest_set (GTK_WIDGET (view),
1201                                    GTK_DEST_DEFAULT_ALL,
1202                                    drag_types_dest,
1203                                    G_N_ELEMENTS (drag_types_dest),
1204                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1205         } else {
1206                 /* FIXME: URI could still be droped depending on FT feature */
1207                 gtk_drag_dest_unset (GTK_WIDGET (view));
1208         }
1209
1210         /* Update has-tooltip */
1211         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1212         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1213 }
1214
1215 static void
1216 contact_list_view_finalize (GObject *object)
1217 {
1218         EmpathyContactListViewPriv *priv;
1219
1220         priv = GET_PRIV (object);
1221
1222         if (priv->store) {
1223                 g_object_unref (priv->store);
1224         }
1225         if (priv->tooltip_widget) {
1226                 gtk_widget_destroy (priv->tooltip_widget);
1227         }
1228
1229         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1230 }
1231
1232 static void
1233 contact_list_view_get_property (GObject    *object,
1234                                 guint       param_id,
1235                                 GValue     *value,
1236                                 GParamSpec *pspec)
1237 {
1238         EmpathyContactListViewPriv *priv;
1239
1240         priv = GET_PRIV (object);
1241
1242         switch (param_id) {
1243         case PROP_STORE:
1244                 g_value_set_object (value, priv->store);
1245                 break;
1246         case PROP_LIST_FEATURES:
1247                 g_value_set_flags (value, priv->list_features);
1248                 break;
1249         case PROP_CONTACT_FEATURES:
1250                 g_value_set_flags (value, priv->contact_features);
1251                 break;
1252         default:
1253                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1254                 break;
1255         };
1256 }
1257
1258 static void
1259 contact_list_view_set_property (GObject      *object,
1260                                 guint         param_id,
1261                                 const GValue *value,
1262                                 GParamSpec   *pspec)
1263 {
1264         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1265         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1266
1267         switch (param_id) {
1268         case PROP_STORE:
1269                 priv->store = g_value_dup_object (value);
1270                 contact_list_view_setup (view);
1271                 break;
1272         case PROP_LIST_FEATURES:
1273                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1274                 break;
1275         case PROP_CONTACT_FEATURES:
1276                 priv->contact_features = g_value_get_flags (value);
1277                 break;
1278         default:
1279                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1280                 break;
1281         };
1282 }
1283
1284 static void
1285 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1286 {
1287         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1288         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1289         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1290
1291         object_class->finalize = contact_list_view_finalize;
1292         object_class->get_property = contact_list_view_get_property;
1293         object_class->set_property = contact_list_view_set_property;
1294
1295         widget_class->drag_data_received = contact_list_view_drag_data_received;
1296         widget_class->drag_drop          = contact_list_view_drag_drop;
1297         widget_class->drag_begin         = contact_list_view_drag_begin;
1298         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1299         widget_class->drag_end           = contact_list_view_drag_end;
1300         widget_class->drag_motion        = contact_list_view_drag_motion;
1301
1302         /* We use the class method to let user of this widget to connect to
1303          * the signal and stop emission of the signal so the default handler
1304          * won't be called. */
1305         tree_view_class->row_activated = contact_list_view_row_activated;
1306
1307         signals[DRAG_CONTACT_RECEIVED] =
1308                 g_signal_new ("drag-contact-received",
1309                               G_OBJECT_CLASS_TYPE (klass),
1310                               G_SIGNAL_RUN_LAST,
1311                               0,
1312                               NULL, NULL,
1313                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1314                               G_TYPE_NONE,
1315                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1316
1317         g_object_class_install_property (object_class,
1318                                          PROP_STORE,
1319                                          g_param_spec_object ("store",
1320                                                              "The store of the view",
1321                                                              "The store of the view",
1322                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1323                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1324         g_object_class_install_property (object_class,
1325                                          PROP_LIST_FEATURES,
1326                                          g_param_spec_flags ("list-features",
1327                                                              "Features of the view",
1328                                                              "Falgs for all enabled features",
1329                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1330                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1331                                                               G_PARAM_READWRITE));
1332         g_object_class_install_property (object_class,
1333                                          PROP_CONTACT_FEATURES,
1334                                          g_param_spec_flags ("contact-features",
1335                                                              "Features of the contact menu",
1336                                                              "Falgs for all enabled features for the menu",
1337                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1338                                                               EMPATHY_CONTACT_FEATURE_NONE,
1339                                                               G_PARAM_READWRITE));
1340
1341         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1342 }
1343
1344 static void
1345 empathy_contact_list_view_init (EmpathyContactListView *view)
1346 {
1347         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1348                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1349
1350         view->priv = priv;
1351         /* Get saved group states. */
1352         empathy_contact_groups_get_all ();
1353
1354         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1355                                               empathy_contact_list_store_row_separator_func,
1356                                               NULL, NULL);
1357
1358         /* Connect to tree view signals rather than override. */
1359         g_signal_connect (view, "button-press-event",
1360                           G_CALLBACK (contact_list_view_button_press_event_cb),
1361                           NULL);
1362         g_signal_connect (view, "key-press-event",
1363                           G_CALLBACK (contact_list_view_key_press_event_cb),
1364                           NULL);
1365         g_signal_connect (view, "row-expanded",
1366                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1367                           GINT_TO_POINTER (TRUE));
1368         g_signal_connect (view, "row-collapsed",
1369                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1370                           GINT_TO_POINTER (FALSE));
1371         g_signal_connect (view, "query-tooltip",
1372                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1373                           NULL);
1374 }
1375
1376 EmpathyContactListView *
1377 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1378                                EmpathyContactListFeatureFlags  list_features,
1379                                EmpathyContactFeatureFlags      contact_features)
1380 {
1381         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1382
1383         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1384                              "store", store,
1385                              "contact-features", contact_features,
1386                              "list-features", list_features,
1387                              NULL);
1388 }
1389
1390 EmpathyContact *
1391 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1392 {
1393         EmpathyContactListViewPriv *priv;
1394         GtkTreeSelection          *selection;
1395         GtkTreeIter                iter;
1396         GtkTreeModel              *model;
1397         EmpathyContact             *contact;
1398
1399         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1400
1401         priv = GET_PRIV (view);
1402
1403         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1404         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1405                 return NULL;
1406         }
1407
1408         gtk_tree_model_get (model, &iter,
1409                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1410                             -1);
1411
1412         return contact;
1413 }
1414
1415 EmpathyContactListFlags
1416 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1417 {
1418         EmpathyContactListViewPriv *priv;
1419         GtkTreeSelection          *selection;
1420         GtkTreeIter                iter;
1421         GtkTreeModel              *model;
1422         EmpathyContactListFlags    flags;
1423
1424         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1425
1426         priv = GET_PRIV (view);
1427
1428         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1429         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1430                 return 0;
1431         }
1432
1433         gtk_tree_model_get (model, &iter,
1434                             EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1435                             -1);
1436
1437         return flags;
1438 }
1439
1440 gchar *
1441 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1442 {
1443         EmpathyContactListViewPriv *priv;
1444         GtkTreeSelection          *selection;
1445         GtkTreeIter                iter;
1446         GtkTreeModel              *model;
1447         gboolean                   is_group;
1448         gchar                     *name;
1449
1450         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1451
1452         priv = GET_PRIV (view);
1453
1454         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1455         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1456                 return NULL;
1457         }
1458
1459         gtk_tree_model_get (model, &iter,
1460                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1461                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1462                             -1);
1463
1464         if (!is_group) {
1465                 g_free (name);
1466                 return NULL;
1467         }
1468
1469         return name;
1470 }
1471
1472 static gboolean
1473 contact_list_view_remove_dialog_show (GtkWindow   *parent,
1474                                       const gchar *message,
1475                                       const gchar *secondary_text)
1476 {
1477         GtkWidget *dialog;
1478         gboolean res;
1479
1480         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1481                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1482                                          "%s", message);
1483         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1484                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1485                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1486                                 NULL);
1487         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1488                                                   "%s", secondary_text);
1489
1490         gtk_widget_show (dialog);
1491
1492         res = gtk_dialog_run (GTK_DIALOG (dialog));
1493         gtk_widget_destroy (dialog);
1494
1495         return (res == GTK_RESPONSE_YES);
1496 }
1497
1498 static void
1499 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1500                                             EmpathyContactListView *view)
1501 {
1502         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1503         gchar                      *group;
1504
1505         group = empathy_contact_list_view_get_selected_group (view);
1506         if (group) {
1507                 gchar     *text;
1508                 GtkWindow *parent;
1509
1510                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1511                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1512                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1513                         EmpathyContactList *list;
1514
1515                         list = empathy_contact_list_store_get_list_iface (priv->store);
1516                         empathy_contact_list_remove_group (list, group);
1517                 }
1518
1519                 g_free (text);
1520         }
1521
1522         g_free (group);
1523 }
1524
1525 GtkWidget *
1526 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1527 {
1528         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1529         gchar                      *group;
1530         GtkWidget                  *menu;
1531         GtkWidget                  *item;
1532         GtkWidget                  *image;
1533
1534         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1535
1536         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1537                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1538                 return NULL;
1539         }
1540
1541         group = empathy_contact_list_view_get_selected_group (view);
1542         if (!group) {
1543                 return NULL;
1544         }
1545
1546         menu = gtk_menu_new ();
1547
1548         /* FIXME: Not implemented yet
1549         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1550                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1551                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1552                 gtk_widget_show (item);
1553                 g_signal_connect (item, "activate",
1554                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1555                                   view);
1556         }*/
1557
1558         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1559                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1560                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1561                                                       GTK_ICON_SIZE_MENU);
1562                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1563                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1564                 gtk_widget_show (item);
1565                 g_signal_connect (item, "activate",
1566                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1567                                   view);
1568         }
1569
1570         g_free (group);
1571
1572         return menu;
1573 }
1574
1575 static void
1576 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1577                                       EmpathyContactListView *view)
1578 {
1579         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1580         EmpathyContact             *contact;
1581
1582         contact = empathy_contact_list_view_dup_selected (view);
1583
1584         if (contact) {
1585                 gchar     *text;
1586                 GtkWindow *parent;
1587
1588                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1589                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1590                                         empathy_contact_get_name (contact));
1591                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1592                         EmpathyContactList *list;
1593
1594                         list = empathy_contact_list_store_get_list_iface (priv->store);
1595                         empathy_contact_list_remove (list, contact, "");
1596                 }
1597
1598                 g_free (text);
1599                 g_object_unref (contact);
1600         }
1601 }
1602
1603 GtkWidget *
1604 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1605 {
1606         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1607         EmpathyContact             *contact;
1608         GtkWidget                  *menu;
1609         GtkWidget                  *item;
1610         GtkWidget                  *image;
1611         EmpathyContactListFlags     flags;
1612
1613         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1614
1615         contact = empathy_contact_list_view_dup_selected (view);
1616         if (!contact) {
1617                 return NULL;
1618         }
1619         flags = empathy_contact_list_view_get_flags (view);
1620
1621         menu = empathy_contact_menu_new (contact, priv->contact_features);
1622
1623         /* Remove contact */
1624         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1625             flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1626                 /* create the menu if required, or just add a separator */
1627                 if (!menu) {
1628                         menu = gtk_menu_new ();
1629                 } else {
1630                         item = gtk_separator_menu_item_new ();
1631                         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1632                         gtk_widget_show (item);
1633                 }
1634
1635                 /* Remove */
1636                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1637                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1638                                                       GTK_ICON_SIZE_MENU);
1639                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1640                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1641                 gtk_widget_show (item);
1642                 g_signal_connect (item, "activate",
1643                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1644                                   view);
1645         }
1646
1647         g_object_unref (contact);
1648
1649         return menu;
1650 }
1651