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