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