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