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