]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Display a context menu when clicking on audio/video icons (#590051)
[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_view_call_activated_cb (
756     EmpathyCellRendererActivatable *cell,
757     const gchar                    *path_string,
758     EmpathyContactListView         *view)
759 {
760         GtkWidget *menu;
761         GtkTreeModel *model;
762         GtkTreeIter iter;
763         EmpathyContact *contact;
764         GdkEventButton *event;
765         GtkMenuShell *shell;
766         GtkWidget *item;
767
768         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
769         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
770                 return;
771
772         gtk_tree_model_get (model, &iter,
773                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
774                             -1);
775         if (contact == NULL)
776                 return;
777
778         event = (GdkEventButton *) gtk_get_current_event ();
779
780         menu = gtk_menu_new ();
781         shell = GTK_MENU_SHELL (menu);
782
783         /* audio */
784         item = empathy_contact_audio_call_menu_item_new (contact);
785         gtk_menu_shell_append (shell, item);
786         gtk_widget_show (item);
787
788         /* video */
789         item = empathy_contact_video_call_menu_item_new (contact);
790         gtk_menu_shell_append (shell, item);
791         gtk_widget_show (item);
792
793         gtk_widget_show (menu);
794         gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
795                         event->button, event->time);
796
797         g_object_unref (contact);
798 }
799
800 static void
801 contact_list_view_cell_set_background (EmpathyContactListView *view,
802                                        GtkCellRenderer       *cell,
803                                        gboolean               is_group,
804                                        gboolean               is_active)
805 {
806         GdkColor  color;
807         GtkStyle *style;
808
809         style = gtk_widget_get_style (GTK_WIDGET (view));
810
811         if (!is_group && is_active) {
812                 color = style->bg[GTK_STATE_SELECTED];
813
814                 /* Here we take the current theme colour and add it to
815                  * the colour for white and average the two. This
816                  * gives a colour which is inline with the theme but
817                  * slightly whiter.
818                  */
819                 color.red = (color.red + (style->white).red) / 2;
820                 color.green = (color.green + (style->white).green) / 2;
821                 color.blue = (color.blue + (style->white).blue) / 2;
822
823                 g_object_set (cell,
824                               "cell-background-gdk", &color,
825                               NULL);
826         } else {
827                 g_object_set (cell,
828                               "cell-background-gdk", NULL,
829                               NULL);
830         }
831 }
832
833 static void
834 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
835                                          GtkCellRenderer       *cell,
836                                          GtkTreeModel          *model,
837                                          GtkTreeIter           *iter,
838                                          EmpathyContactListView *view)
839 {
840         GdkPixbuf *pixbuf;
841         gboolean   is_group;
842         gboolean   is_active;
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_ICON_STATUS, &pixbuf,
848                             -1);
849
850         g_object_set (cell,
851                       "visible", !is_group,
852                       "pixbuf", pixbuf,
853                       NULL);
854
855         if (pixbuf != NULL) {
856                 g_object_unref (pixbuf);
857         }
858
859         contact_list_view_cell_set_background (view, cell, is_group, is_active);
860 }
861
862 static void
863 contact_list_view_audio_call_cell_data_func (
864                                        GtkTreeViewColumn      *tree_column,
865                                        GtkCellRenderer        *cell,
866                                        GtkTreeModel           *model,
867                                        GtkTreeIter            *iter,
868                                        EmpathyContactListView *view)
869 {
870         gboolean is_group;
871         gboolean is_active;
872         gboolean can_voip;
873
874         gtk_tree_model_get (model, iter,
875                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
876                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
877                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_AUDIO_CALL, &can_voip,
878                             -1);
879
880         g_object_set (cell,
881                       "visible", !is_group && can_voip,
882                       "icon-name", EMPATHY_IMAGE_VOIP,
883                       NULL);
884
885         contact_list_view_cell_set_background (view, cell, is_group, is_active);
886 }
887
888 static void
889 contact_list_view_video_call_cell_data_func (
890                                        GtkTreeViewColumn      *tree_column,
891                                        GtkCellRenderer        *cell,
892                                        GtkTreeModel           *model,
893                                        GtkTreeIter            *iter,
894                                        EmpathyContactListView *view)
895 {
896         gboolean is_group;
897         gboolean is_active;
898         gboolean can_voip;
899
900         gtk_tree_model_get (model, iter,
901                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
902                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
903                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VIDEO_CALL, &can_voip,
904                             -1);
905
906         g_object_set (cell,
907                       "visible", !is_group && can_voip,
908                       "icon-name", EMPATHY_IMAGE_VIDEO_CALL,
909                       NULL);
910
911         contact_list_view_cell_set_background (view, cell, is_group, is_active);
912 }
913
914
915 static void
916 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
917                                          GtkCellRenderer       *cell,
918                                          GtkTreeModel          *model,
919                                          GtkTreeIter           *iter,
920                                          EmpathyContactListView *view)
921 {
922         GdkPixbuf *pixbuf;
923         gboolean   show_avatar;
924         gboolean   is_group;
925         gboolean   is_active;
926
927         gtk_tree_model_get (model, iter,
928                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
929                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
930                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
931                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
932                             -1);
933
934         g_object_set (cell,
935                       "visible", !is_group && show_avatar,
936                       "pixbuf", pixbuf,
937                       NULL);
938
939         if (pixbuf) {
940                 g_object_unref (pixbuf);
941         }
942
943         contact_list_view_cell_set_background (view, cell, is_group, is_active);
944 }
945
946 static void
947 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
948                                        GtkCellRenderer       *cell,
949                                        GtkTreeModel          *model,
950                                        GtkTreeIter           *iter,
951                                        EmpathyContactListView *view)
952 {
953         gboolean is_group;
954         gboolean is_active;
955         gboolean show_status;
956         gchar *name;
957
958         gtk_tree_model_get (model, iter,
959                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
960                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
961                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
962                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
963                             -1);
964
965         g_object_set (cell,
966                       "show-status", show_status,
967                       "text", name,
968                       NULL);
969         g_free (name);
970
971         contact_list_view_cell_set_background (view, cell, is_group, is_active);
972 }
973
974 static void
975 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
976                                            GtkCellRenderer       *cell,
977                                            GtkTreeModel          *model,
978                                            GtkTreeIter           *iter,
979                                            EmpathyContactListView *view)
980 {
981         gboolean is_group;
982         gboolean is_active;
983
984         gtk_tree_model_get (model, iter,
985                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
986                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
987                             -1);
988
989         if (gtk_tree_model_iter_has_child (model, iter)) {
990                 GtkTreePath *path;
991                 gboolean     row_expanded;
992
993                 path = gtk_tree_model_get_path (model, iter);
994                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (gtk_tree_view_column_get_tree_view (column)), path);
995                 gtk_tree_path_free (path);
996
997                 g_object_set (cell,
998                               "visible", TRUE,
999                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1000                               NULL);
1001         } else {
1002                 g_object_set (cell, "visible", FALSE, NULL);
1003         }
1004
1005         contact_list_view_cell_set_background (view, cell, is_group, is_active);
1006 }
1007
1008 static void
1009 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
1010                                              GtkTreeIter           *iter,
1011                                              GtkTreePath           *path,
1012                                              gpointer               user_data)
1013 {
1014         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1015         GtkTreeModel               *model;
1016         gchar                      *name;
1017         gboolean                    expanded;
1018
1019         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
1020                 return;
1021         }
1022
1023         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1024
1025         gtk_tree_model_get (model, iter,
1026                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1027                             -1);
1028
1029         expanded = GPOINTER_TO_INT (user_data);
1030         empathy_contact_group_set_expanded (name, expanded);
1031
1032         g_free (name);
1033 }
1034
1035 static void
1036 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
1037                                             GtkTreePath           *path,
1038                                             GtkTreeIter           *iter,
1039                                             EmpathyContactListView *view)
1040 {
1041         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1042         gboolean  is_group = FALSE;
1043         gchar    *name = NULL;
1044
1045         gtk_tree_model_get (model, iter,
1046                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1047                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1048                             -1);
1049
1050         if (!is_group || EMP_STR_EMPTY (name)) {
1051                 g_free (name);
1052                 return;
1053         }
1054
1055         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
1056             empathy_contact_group_get_expanded (name)) {
1057                 g_signal_handlers_block_by_func (view,
1058                                                  contact_list_view_row_expand_or_collapse_cb,
1059                                                  GINT_TO_POINTER (TRUE));
1060                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1061                 g_signal_handlers_unblock_by_func (view,
1062                                                    contact_list_view_row_expand_or_collapse_cb,
1063                                                    GINT_TO_POINTER (TRUE));
1064         } else {
1065                 g_signal_handlers_block_by_func (view,
1066                                                  contact_list_view_row_expand_or_collapse_cb,
1067                                                  GINT_TO_POINTER (FALSE));
1068                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1069                 g_signal_handlers_unblock_by_func (view,
1070                                                    contact_list_view_row_expand_or_collapse_cb,
1071                                                    GINT_TO_POINTER (FALSE));
1072         }
1073
1074         g_free (name);
1075 }
1076
1077 static void
1078 contact_list_view_setup (EmpathyContactListView *view)
1079 {
1080         EmpathyContactListViewPriv *priv;
1081         GtkCellRenderer           *cell;
1082         GtkTreeViewColumn         *col;
1083         guint                      i;
1084
1085         priv = GET_PRIV (view);
1086
1087         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
1088                                              empathy_contact_list_store_search_equal_func,
1089                                              NULL, NULL);
1090
1091         g_signal_connect (priv->store, "row-has-child-toggled",
1092                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
1093                           view);
1094         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1095                                  GTK_TREE_MODEL (priv->store));
1096
1097         /* Setup view */
1098         /* Setting reorderable is a hack that gets us row previews as drag icons
1099            for free.  We override all the drag handlers.  It's tricky to get the
1100            position of the drag icon right in drag_begin.  GtkTreeView has special
1101            voodoo for it, so we let it do the voodoo that he do.
1102          */
1103         g_object_set (view,
1104                       "headers-visible", FALSE,
1105                       "reorderable", TRUE,
1106                       "show-expanders", FALSE,
1107                       NULL);
1108
1109         col = gtk_tree_view_column_new ();
1110
1111         /* State */
1112         cell = gtk_cell_renderer_pixbuf_new ();
1113         gtk_tree_view_column_pack_start (col, cell, FALSE);
1114         gtk_tree_view_column_set_cell_data_func (
1115                 col, cell,
1116                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
1117                 view, NULL);
1118
1119         g_object_set (cell,
1120                       "xpad", 5,
1121                       "ypad", 1,
1122                       "visible", FALSE,
1123                       NULL);
1124
1125         /* Name */
1126         cell = empathy_cell_renderer_text_new ();
1127         gtk_tree_view_column_pack_start (col, cell, TRUE);
1128         gtk_tree_view_column_set_cell_data_func (
1129                 col, cell,
1130                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
1131                 view, NULL);
1132
1133         gtk_tree_view_column_add_attribute (col, cell,
1134                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
1135         gtk_tree_view_column_add_attribute (col, cell,
1136                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
1137         gtk_tree_view_column_add_attribute (col, cell,
1138                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
1139
1140         /* Audio Call Icon */
1141         cell = empathy_cell_renderer_activatable_new ();
1142         gtk_tree_view_column_pack_start (col, cell, FALSE);
1143         gtk_tree_view_column_set_cell_data_func (
1144                 col, cell,
1145                 (GtkTreeCellDataFunc) contact_list_view_audio_call_cell_data_func,
1146                 view, NULL);
1147
1148         g_object_set (cell,
1149                       "visible", FALSE,
1150                       NULL);
1151
1152         g_signal_connect (cell, "path-activated",
1153                           G_CALLBACK (contact_list_view_call_activated_cb),
1154                           view);
1155
1156         /* Video Call Icon */
1157         cell = empathy_cell_renderer_activatable_new ();
1158         gtk_tree_view_column_pack_start (col, cell, FALSE);
1159         gtk_tree_view_column_set_cell_data_func (
1160                 col, cell,
1161                 (GtkTreeCellDataFunc) contact_list_view_video_call_cell_data_func,
1162                 view, NULL);
1163
1164         g_object_set (cell,
1165                       "visible", FALSE,
1166                       NULL);
1167
1168         g_signal_connect (cell, "path-activated",
1169                           G_CALLBACK (contact_list_view_call_activated_cb),
1170                           view);
1171
1172         /* Avatar */
1173         cell = gtk_cell_renderer_pixbuf_new ();
1174         gtk_tree_view_column_pack_start (col, cell, FALSE);
1175         gtk_tree_view_column_set_cell_data_func (
1176                 col, cell,
1177                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
1178                 view, NULL);
1179
1180         g_object_set (cell,
1181                       "xpad", 0,
1182                       "ypad", 0,
1183                       "visible", FALSE,
1184                       "width", 32,
1185                       "height", 32,
1186                       NULL);
1187
1188         /* Expander */
1189         cell = empathy_cell_renderer_expander_new ();
1190         gtk_tree_view_column_pack_end (col, cell, FALSE);
1191         gtk_tree_view_column_set_cell_data_func (
1192                 col, cell,
1193                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
1194                 view, NULL);
1195
1196         /* Actually add the column now we have added all cell renderers */
1197         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1198
1199         /* Drag & Drop. */
1200         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
1201                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
1202                                                       FALSE);
1203         }
1204
1205         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
1206                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1207                                                         FALSE);
1208         }
1209 }
1210
1211 static void
1212 contact_list_view_set_list_features (EmpathyContactListView         *view,
1213                                      EmpathyContactListFeatureFlags  features)
1214 {
1215         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1216         gboolean                    has_tooltip;
1217
1218         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
1219
1220         priv->list_features = features;
1221
1222         /* Update DnD source/dest */
1223         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
1224                 gtk_drag_source_set (GTK_WIDGET (view),
1225                                      GDK_BUTTON1_MASK,
1226                                      drag_types_source,
1227                                      G_N_ELEMENTS (drag_types_source),
1228                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
1229         } else {
1230                 gtk_drag_source_unset (GTK_WIDGET (view));
1231
1232         }
1233
1234         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
1235                 gtk_drag_dest_set (GTK_WIDGET (view),
1236                                    GTK_DEST_DEFAULT_ALL,
1237                                    drag_types_dest,
1238                                    G_N_ELEMENTS (drag_types_dest),
1239                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
1240         } else {
1241                 /* FIXME: URI could still be droped depending on FT feature */
1242                 gtk_drag_dest_unset (GTK_WIDGET (view));
1243         }
1244
1245         /* Update has-tooltip */
1246         has_tooltip = (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_TOOLTIP) != 0;
1247         gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1248 }
1249
1250 static void
1251 contact_list_view_finalize (GObject *object)
1252 {
1253         EmpathyContactListViewPriv *priv;
1254
1255         priv = GET_PRIV (object);
1256
1257         if (priv->store) {
1258                 g_object_unref (priv->store);
1259         }
1260         if (priv->tooltip_widget) {
1261                 gtk_widget_destroy (priv->tooltip_widget);
1262         }
1263         if (priv->file_targets) {
1264                 gtk_target_list_unref (priv->file_targets);
1265         }
1266
1267         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
1268 }
1269
1270 static void
1271 contact_list_view_get_property (GObject    *object,
1272                                 guint       param_id,
1273                                 GValue     *value,
1274                                 GParamSpec *pspec)
1275 {
1276         EmpathyContactListViewPriv *priv;
1277
1278         priv = GET_PRIV (object);
1279
1280         switch (param_id) {
1281         case PROP_STORE:
1282                 g_value_set_object (value, priv->store);
1283                 break;
1284         case PROP_LIST_FEATURES:
1285                 g_value_set_flags (value, priv->list_features);
1286                 break;
1287         case PROP_CONTACT_FEATURES:
1288                 g_value_set_flags (value, priv->contact_features);
1289                 break;
1290         default:
1291                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1292                 break;
1293         };
1294 }
1295
1296 static void
1297 contact_list_view_set_property (GObject      *object,
1298                                 guint         param_id,
1299                                 const GValue *value,
1300                                 GParamSpec   *pspec)
1301 {
1302         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
1303         EmpathyContactListViewPriv *priv = GET_PRIV (object);
1304
1305         switch (param_id) {
1306         case PROP_STORE:
1307                 priv->store = g_value_dup_object (value);
1308                 contact_list_view_setup (view);
1309                 break;
1310         case PROP_LIST_FEATURES:
1311                 contact_list_view_set_list_features (view, g_value_get_flags (value));
1312                 break;
1313         case PROP_CONTACT_FEATURES:
1314                 priv->contact_features = g_value_get_flags (value);
1315                 break;
1316         default:
1317                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1318                 break;
1319         };
1320 }
1321
1322 static void
1323 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
1324 {
1325         GObjectClass     *object_class = G_OBJECT_CLASS (klass);
1326         GtkWidgetClass   *widget_class = GTK_WIDGET_CLASS (klass);
1327         GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1328
1329         object_class->finalize = contact_list_view_finalize;
1330         object_class->get_property = contact_list_view_get_property;
1331         object_class->set_property = contact_list_view_set_property;
1332
1333         widget_class->drag_data_received = contact_list_view_drag_data_received;
1334         widget_class->drag_drop          = contact_list_view_drag_drop;
1335         widget_class->drag_begin         = contact_list_view_drag_begin;
1336         widget_class->drag_data_get      = contact_list_view_drag_data_get;
1337         widget_class->drag_end           = contact_list_view_drag_end;
1338         widget_class->drag_motion        = contact_list_view_drag_motion;
1339
1340         /* We use the class method to let user of this widget to connect to
1341          * the signal and stop emission of the signal so the default handler
1342          * won't be called. */
1343         tree_view_class->row_activated = contact_list_view_row_activated;
1344
1345         signals[DRAG_CONTACT_RECEIVED] =
1346                 g_signal_new ("drag-contact-received",
1347                               G_OBJECT_CLASS_TYPE (klass),
1348                               G_SIGNAL_RUN_LAST,
1349                               0,
1350                               NULL, NULL,
1351                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1352                               G_TYPE_NONE,
1353                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1354
1355         g_object_class_install_property (object_class,
1356                                          PROP_STORE,
1357                                          g_param_spec_object ("store",
1358                                                              "The store of the view",
1359                                                              "The store of the view",
1360                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
1361                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1362         g_object_class_install_property (object_class,
1363                                          PROP_LIST_FEATURES,
1364                                          g_param_spec_flags ("list-features",
1365                                                              "Features of the view",
1366                                                              "Falgs for all enabled features",
1367                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1368                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1369                                                               G_PARAM_READWRITE));
1370         g_object_class_install_property (object_class,
1371                                          PROP_CONTACT_FEATURES,
1372                                          g_param_spec_flags ("contact-features",
1373                                                              "Features of the contact menu",
1374                                                              "Falgs for all enabled features for the menu",
1375                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1376                                                               EMPATHY_CONTACT_FEATURE_NONE,
1377                                                               G_PARAM_READWRITE));
1378
1379         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1380 }
1381
1382 static void
1383 empathy_contact_list_view_init (EmpathyContactListView *view)
1384 {
1385         EmpathyContactListViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1386                 EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv);
1387
1388         view->priv = priv;
1389         /* Get saved group states. */
1390         empathy_contact_groups_get_all ();
1391
1392         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1393                                               empathy_contact_list_store_row_separator_func,
1394                                               NULL, NULL);
1395
1396         /* Set up drag target lists. */
1397         priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1398                                                   G_N_ELEMENTS (drag_types_dest_file));
1399
1400         /* Connect to tree view signals rather than override. */
1401         g_signal_connect (view, "button-press-event",
1402                           G_CALLBACK (contact_list_view_button_press_event_cb),
1403                           NULL);
1404         g_signal_connect (view, "key-press-event",
1405                           G_CALLBACK (contact_list_view_key_press_event_cb),
1406                           NULL);
1407         g_signal_connect (view, "row-expanded",
1408                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1409                           GINT_TO_POINTER (TRUE));
1410         g_signal_connect (view, "row-collapsed",
1411                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1412                           GINT_TO_POINTER (FALSE));
1413         g_signal_connect (view, "query-tooltip",
1414                           G_CALLBACK (contact_list_view_query_tooltip_cb),
1415                           NULL);
1416 }
1417
1418 EmpathyContactListView *
1419 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1420                                EmpathyContactListFeatureFlags  list_features,
1421                                EmpathyContactFeatureFlags      contact_features)
1422 {
1423         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1424
1425         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1426                              "store", store,
1427                              "contact-features", contact_features,
1428                              "list-features", list_features,
1429                              NULL);
1430 }
1431
1432 EmpathyContact *
1433 empathy_contact_list_view_dup_selected (EmpathyContactListView *view)
1434 {
1435         EmpathyContactListViewPriv *priv;
1436         GtkTreeSelection          *selection;
1437         GtkTreeIter                iter;
1438         GtkTreeModel              *model;
1439         EmpathyContact             *contact;
1440
1441         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1442
1443         priv = GET_PRIV (view);
1444
1445         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1446         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1447                 return NULL;
1448         }
1449
1450         gtk_tree_model_get (model, &iter,
1451                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1452                             -1);
1453
1454         return contact;
1455 }
1456
1457 EmpathyContactListFlags
1458 empathy_contact_list_view_get_flags (EmpathyContactListView *view)
1459 {
1460         EmpathyContactListViewPriv *priv;
1461         GtkTreeSelection          *selection;
1462         GtkTreeIter                iter;
1463         GtkTreeModel              *model;
1464         EmpathyContactListFlags    flags;
1465
1466         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), 0);
1467
1468         priv = GET_PRIV (view);
1469
1470         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1471         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1472                 return 0;
1473         }
1474
1475         gtk_tree_model_get (model, &iter,
1476                             EMPATHY_CONTACT_LIST_STORE_COL_FLAGS, &flags,
1477                             -1);
1478
1479         return flags;
1480 }
1481
1482 gchar *
1483 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1484 {
1485         EmpathyContactListViewPriv *priv;
1486         GtkTreeSelection          *selection;
1487         GtkTreeIter                iter;
1488         GtkTreeModel              *model;
1489         gboolean                   is_group;
1490         gchar                     *name;
1491
1492         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1493
1494         priv = GET_PRIV (view);
1495
1496         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1497         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1498                 return NULL;
1499         }
1500
1501         gtk_tree_model_get (model, &iter,
1502                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1503                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1504                             -1);
1505
1506         if (!is_group) {
1507                 g_free (name);
1508                 return NULL;
1509         }
1510
1511         return name;
1512 }
1513
1514 static gboolean
1515 contact_list_view_remove_dialog_show (GtkWindow   *parent,
1516                                       const gchar *message,
1517                                       const gchar *secondary_text)
1518 {
1519         GtkWidget *dialog;
1520         gboolean res;
1521
1522         dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1523                                          GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
1524                                          "%s", message);
1525         gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1526                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1527                                 GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1528                                 NULL);
1529         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1530                                                   "%s", secondary_text);
1531
1532         gtk_widget_show (dialog);
1533
1534         res = gtk_dialog_run (GTK_DIALOG (dialog));
1535         gtk_widget_destroy (dialog);
1536
1537         return (res == GTK_RESPONSE_YES);
1538 }
1539
1540 static void
1541 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1542                                             EmpathyContactListView *view)
1543 {
1544         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1545         gchar                      *group;
1546
1547         group = empathy_contact_list_view_get_selected_group (view);
1548         if (group) {
1549                 gchar     *text;
1550                 GtkWindow *parent;
1551
1552                 text = g_strdup_printf (_("Do you really want to remove the group '%s'?"), group);
1553                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1554                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1555                         EmpathyContactList *list;
1556
1557                         list = empathy_contact_list_store_get_list_iface (priv->store);
1558                         empathy_contact_list_remove_group (list, group);
1559                 }
1560
1561                 g_free (text);
1562         }
1563
1564         g_free (group);
1565 }
1566
1567 GtkWidget *
1568 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1569 {
1570         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1571         gchar                      *group;
1572         GtkWidget                  *menu;
1573         GtkWidget                  *item;
1574         GtkWidget                  *image;
1575
1576         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1577
1578         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1579                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1580                 return NULL;
1581         }
1582
1583         group = empathy_contact_list_view_get_selected_group (view);
1584         if (!group) {
1585                 return NULL;
1586         }
1587
1588         menu = gtk_menu_new ();
1589
1590         /* FIXME: Not implemented yet
1591         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1592                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1593                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1594                 gtk_widget_show (item);
1595                 g_signal_connect (item, "activate",
1596                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1597                                   view);
1598         }*/
1599
1600         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1601                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1602                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1603                                                       GTK_ICON_SIZE_MENU);
1604                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1605                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1606                 gtk_widget_show (item);
1607                 g_signal_connect (item, "activate",
1608                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1609                                   view);
1610         }
1611
1612         g_free (group);
1613
1614         return menu;
1615 }
1616
1617 static void
1618 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1619                                       EmpathyContactListView *view)
1620 {
1621         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1622         EmpathyContact             *contact;
1623
1624         contact = empathy_contact_list_view_dup_selected (view);
1625
1626         if (contact) {
1627                 gchar     *text;
1628                 GtkWindow *parent;
1629
1630                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1631                 text = g_strdup_printf (_("Do you really want to remove the contact '%s'?"),
1632                                         empathy_contact_get_name (contact));
1633                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1634                         EmpathyContactList *list;
1635
1636                         list = empathy_contact_list_store_get_list_iface (priv->store);
1637                         empathy_contact_list_remove (list, contact, "");
1638                 }
1639
1640                 g_free (text);
1641                 g_object_unref (contact);
1642         }
1643 }
1644
1645 GtkWidget *
1646 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1647 {
1648         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1649         EmpathyContact             *contact;
1650         GtkWidget                  *menu;
1651         GtkWidget                  *item;
1652         GtkWidget                  *image;
1653         EmpathyContactListFlags     flags;
1654
1655         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1656
1657         contact = empathy_contact_list_view_dup_selected (view);
1658         if (!contact) {
1659                 return NULL;
1660         }
1661         flags = empathy_contact_list_view_get_flags (view);
1662
1663         menu = empathy_contact_menu_new (contact, priv->contact_features);
1664
1665         /* Remove contact */
1666         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE &&
1667             flags & EMPATHY_CONTACT_LIST_CAN_REMOVE) {
1668                 /* create the menu if required, or just add a separator */
1669                 if (!menu) {
1670                         menu = gtk_menu_new ();
1671                 } else {
1672                         item = gtk_separator_menu_item_new ();
1673                         gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1674                         gtk_widget_show (item);
1675                 }
1676
1677                 /* Remove */
1678                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1679                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1680                                                       GTK_ICON_SIZE_MENU);
1681                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1682                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1683                 gtk_widget_show (item);
1684                 g_signal_connect (item, "activate",
1685                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1686                                   view);
1687         }
1688
1689         g_object_unref (contact);
1690
1691         return menu;
1692 }
1693