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