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