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