]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-contact-list-view.c
Create contact menu in empathy-contact-menu.h
[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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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.h>
31 #include <gtk/gtk.h>
32 #include <glade/glade.h>
33
34 #include <libmissioncontrol/mc-account.h>
35
36 #include <libempathy/empathy-contact-factory.h>
37 #include <libempathy/empathy-contact-list.h>
38 #include <libempathy/empathy-contact-groups.h>
39 #include <libempathy/empathy-debug.h>
40 #include <libempathy/empathy-utils.h>
41
42 #include "empathy-contact-list-view.h"
43 #include "empathy-contact-list-store.h"
44 #include "empathy-images.h"
45 #include "empathy-cell-renderer-expander.h"
46 #include "empathy-cell-renderer-text.h"
47 #include "empathy-cell-renderer-activatable.h"
48 #include "empathy-ui-utils.h"
49 #include "empathy-gtk-enum-types.h"
50 #include "empathy-gtk-marshal.h"
51
52 #define DEBUG_DOMAIN "ContactListView"
53
54 /* Flashing delay for icons (milliseconds). */
55 #define FLASH_TIMEOUT 500
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) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EMPATHY_TYPE_CONTACT_LIST_VIEW, EmpathyContactListViewPriv))
62
63 typedef struct {
64         EmpathyContactListStore        *store;
65         GtkTreeRowReference            *drag_row;
66         EmpathyContactListFeatureFlags  list_features;
67         EmpathyContactFeatureFlags      contact_features;
68 } EmpathyContactListViewPriv;
69
70 typedef struct {
71         EmpathyContactListView *view;
72         GtkTreePath           *path;
73         guint                  timeout_id;
74 } DragMotionData;
75
76 typedef struct {
77         EmpathyContactListView *view;
78         EmpathyContact         *contact;
79         gboolean               remove;
80 } ShowActiveData;
81
82 enum {
83         PROP_0,
84         PROP_STORE,
85         PROP_LIST_FEATURES,
86         PROP_CONTACT_FEATURES,
87 };
88
89 enum DndDragType {
90         DND_DRAG_TYPE_CONTACT_ID,
91         DND_DRAG_TYPE_URL,
92         DND_DRAG_TYPE_STRING,
93 };
94
95 static const GtkTargetEntry drag_types_dest[] = {
96         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
97         { "text/uri-list",   0, DND_DRAG_TYPE_URL },
98         { "text/plain",      0, DND_DRAG_TYPE_STRING },
99         { "STRING",          0, DND_DRAG_TYPE_STRING },
100 };
101
102 static const GtkTargetEntry drag_types_source[] = {
103         { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID },
104 };
105
106 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
107 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
108
109 enum {
110         DRAG_CONTACT_RECEIVED,
111         LAST_SIGNAL
112 };
113
114 static guint signals[LAST_SIGNAL];
115
116 G_DEFINE_TYPE (EmpathyContactListView, empathy_contact_list_view, GTK_TYPE_TREE_VIEW);
117
118 static void
119 contact_list_view_drag_data_received (GtkWidget         *widget,
120                                       GdkDragContext    *context,
121                                       gint               x,
122                                       gint               y,
123                                       GtkSelectionData  *selection,
124                                       guint              info,
125                                       guint              time)
126 {
127         EmpathyContactListViewPriv *priv;
128         EmpathyContactList         *list;
129         EmpathyContactFactory      *factory;
130         McAccount                  *account;
131         GtkTreeModel               *model;
132         GtkTreePath                *path;
133         GtkTreeViewDropPosition     position;
134         EmpathyContact             *contact = NULL;
135         const gchar                *id;
136         gchar                     **strv;
137         gchar                      *new_group = NULL;
138         gchar                      *old_group = NULL;
139         gboolean                    is_row;
140
141         priv = GET_PRIV (widget);
142
143         id = (const gchar*) selection->data;
144         empathy_debug (DEBUG_DOMAIN, "Received %s%s drag & drop contact from roster with id:'%s'",
145                       context->action == GDK_ACTION_MOVE ? "move" : "",
146                       context->action == GDK_ACTION_COPY ? "copy" : "",
147                       id);
148
149         strv = g_strsplit (id, "/", 2);
150         factory = empathy_contact_factory_new ();
151         account = mc_account_lookup (strv[0]);
152         if (account) {
153                 contact = empathy_contact_factory_get_from_id (factory,
154                                                                account,
155                                                                strv[1]);
156                 g_object_unref (account);
157         }
158         g_object_unref (factory);
159         g_strfreev (strv);
160
161         if (!contact) {
162                 empathy_debug (DEBUG_DOMAIN, "No contact found associated with drag & drop");
163                 return;
164         }
165
166         empathy_contact_run_until_ready (contact,
167                                          EMPATHY_CONTACT_READY_HANDLE,
168                                          NULL);
169
170         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
171
172         /* Get source group information. */
173         if (priv->drag_row) {
174                 path = gtk_tree_row_reference_get_path (priv->drag_row);
175                 if (path) {
176                         old_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
177                         gtk_tree_path_free (path);
178                 }
179         }
180
181         /* Get destination group information. */
182         is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (widget),
183                                                     x,
184                                                     y,
185                                                     &path,
186                                                     &position);
187
188         if (is_row) {
189                 new_group = empathy_contact_list_store_get_parent_group (model, path, NULL);
190                 gtk_tree_path_free (path);
191         }
192
193         empathy_debug (DEBUG_DOMAIN,
194                       "contact %s (%d) dragged from '%s' to '%s'",
195                       empathy_contact_get_id (contact),
196                       empathy_contact_get_handle (contact),
197                       old_group, new_group);
198
199         list = empathy_contact_list_store_get_list_iface (priv->store);
200         if (new_group) {
201                 empathy_contact_list_add_to_group (list, contact, new_group);
202         }
203         if (old_group && context->action == GDK_ACTION_MOVE) {  
204                 empathy_contact_list_remove_from_group (list, contact, old_group);
205         }
206
207         g_free (old_group);
208         g_free (new_group);
209
210         gtk_drag_finish (context, TRUE, FALSE, GDK_CURRENT_TIME);
211 }
212
213 static gboolean
214 contact_list_view_drag_motion_cb (DragMotionData *data)
215 {
216         gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view),
217                                   data->path,
218                                   FALSE);
219
220         data->timeout_id = 0;
221
222         return FALSE;
223 }
224
225 static gboolean
226 contact_list_view_drag_motion (GtkWidget      *widget,
227                                GdkDragContext *context,
228                                gint            x,
229                                gint            y,
230                                guint           time)
231 {
232         static DragMotionData *dm = NULL;
233         GtkTreePath           *path;
234         gboolean               is_row;
235         gboolean               is_different = FALSE;
236         gboolean               cleanup = TRUE;
237
238         is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
239                                                 x,
240                                                 y,
241                                                 &path,
242                                                 NULL,
243                                                 NULL,
244                                                 NULL);
245
246         cleanup &= (!dm);
247
248         if (is_row) {
249                 cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
250                 is_different = (!dm || (dm && gtk_tree_path_compare (dm->path, path) != 0));
251         } else {
252                 cleanup &= FALSE;
253         }
254
255         if (!is_different && !cleanup) {
256                 return TRUE;
257         }
258
259         if (dm) {
260                 gtk_tree_path_free (dm->path);
261                 if (dm->timeout_id) {
262                         g_source_remove (dm->timeout_id);
263                 }
264
265                 g_free (dm);
266
267                 dm = NULL;
268         }
269
270         if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
271                 dm = g_new0 (DragMotionData, 1);
272
273                 dm->view = EMPATHY_CONTACT_LIST_VIEW (widget);
274                 dm->path = gtk_tree_path_copy (path);
275
276                 dm->timeout_id = g_timeout_add_seconds (1,
277                         (GSourceFunc) contact_list_view_drag_motion_cb,
278                         dm);
279         }
280
281         return TRUE;
282 }
283
284 static void
285 contact_list_view_drag_begin (GtkWidget      *widget,
286                               GdkDragContext *context)
287 {
288         EmpathyContactListViewPriv *priv;
289         GtkTreeSelection          *selection;
290         GtkTreeModel              *model;
291         GtkTreePath               *path;
292         GtkTreeIter                iter;
293
294         priv = GET_PRIV (widget);
295
296         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_begin (widget,
297                                                                               context);
298
299         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
300         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
301                 return;
302         }
303
304         path = gtk_tree_model_get_path (model, &iter);
305         priv->drag_row = gtk_tree_row_reference_new (model, path);
306         gtk_tree_path_free (path);
307 }
308
309 static void
310 contact_list_view_drag_data_get (GtkWidget        *widget,
311                                  GdkDragContext   *context,
312                                  GtkSelectionData *selection,
313                                  guint             info,
314                                  guint             time)
315 {
316         EmpathyContactListViewPriv *priv;
317         GtkTreePath                *src_path;
318         GtkTreeIter                 iter;
319         GtkTreeModel               *model;
320         EmpathyContact             *contact;
321         McAccount                  *account;
322         const gchar                *contact_id;
323         const gchar                *account_id;
324         gchar                      *str;
325
326         priv = GET_PRIV (widget);
327
328         model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
329         if (!priv->drag_row) {
330                 return;
331         }
332
333         src_path = gtk_tree_row_reference_get_path (priv->drag_row);
334         if (!src_path) {
335                 return;
336         }
337
338         if (!gtk_tree_model_get_iter (model, &iter, src_path)) {
339                 gtk_tree_path_free (src_path);
340                 return;
341         }
342
343         gtk_tree_path_free (src_path);
344
345         contact = empathy_contact_list_view_get_selected (EMPATHY_CONTACT_LIST_VIEW (widget));
346         if (!contact) {
347                 return;
348         }
349
350         account = empathy_contact_get_account (contact);
351         account_id = mc_account_get_unique_name (account);
352         contact_id = empathy_contact_get_id (contact);
353         g_object_unref (contact);
354         str = g_strconcat (account_id, "/", contact_id, NULL);
355
356         switch (info) {
357         case DND_DRAG_TYPE_CONTACT_ID:
358                 gtk_selection_data_set (selection, drag_atoms_source[info], 8,
359                                         (guchar*)str, strlen (str) + 1);
360                 break;
361         }
362
363         g_free (str);
364 }
365
366 static void
367 contact_list_view_drag_end (GtkWidget      *widget,
368                             GdkDragContext *context)
369 {
370         EmpathyContactListViewPriv *priv;
371
372         priv = GET_PRIV (widget);
373
374         GTK_WIDGET_CLASS (empathy_contact_list_view_parent_class)->drag_end (widget,
375                                                                             context);
376
377         if (priv->drag_row) {
378                 gtk_tree_row_reference_free (priv->drag_row);
379                 priv->drag_row = NULL;
380         }
381 }
382
383 static gboolean
384 contact_list_view_drag_drop (GtkWidget      *widget,
385                              GdkDragContext *drag_context,
386                              gint            x,
387                              gint            y,
388                              guint           time)
389 {
390         return FALSE;
391 }
392
393 typedef struct {
394         EmpathyContactListView *view;
395         guint                   button;
396         guint32                 time;
397 } MenuPopupData;
398
399 static gboolean
400 contact_list_view_popup_menu_idle_cb (gpointer user_data)
401 {
402         MenuPopupData *data = user_data;
403         GtkWidget     *menu;
404
405         menu = empathy_contact_list_view_get_contact_menu (data->view);
406         if (!menu) {
407                 menu = empathy_contact_list_view_get_group_menu (data->view);
408         }
409
410         if (!menu) {
411                 goto OUT;
412         }
413
414         gtk_widget_show (menu);
415
416         gtk_menu_popup (GTK_MENU (menu),
417                         NULL, NULL, NULL, NULL,
418                         data->button, data->time);
419
420 OUT:
421         g_slice_free (MenuPopupData, data);
422
423         return FALSE;
424 }
425
426 static gboolean
427 contact_list_view_button_press_event_cb (EmpathyContactListView *view,
428                                          GdkEventButton         *event,
429                                          gpointer                user_data)
430 {
431         if (event->button == 3) {
432                 MenuPopupData *data;
433
434                 data = g_slice_new (MenuPopupData);
435                 data->view = view;
436                 data->button = event->button;
437                 data->time = event->time;
438                 g_idle_add (contact_list_view_popup_menu_idle_cb, data);
439         }
440
441         return FALSE;
442 }
443
444 static void
445 contact_list_view_row_activated_cb (EmpathyContactListView *view,
446                                     GtkTreePath            *path,
447                                     GtkTreeViewColumn      *col,
448                                     gpointer                user_data)
449 {
450         EmpathyContactListViewPriv *priv = GET_PRIV (view);
451         EmpathyContact             *contact;
452         GtkTreeModel               *model;
453         GtkTreeIter                 iter;
454
455         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CHAT)) {
456                 return;
457         }
458
459         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
460
461         gtk_tree_model_get_iter (model, &iter, path);
462         gtk_tree_model_get (model, &iter,
463                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
464                             -1);
465
466         if (contact) {
467                 empathy_chat_with_contact (contact);
468                 g_object_unref (contact);
469         }
470 }
471
472 static void
473 contact_list_view_voip_activated_cb (EmpathyCellRendererActivatable *cell,
474                                      const gchar                    *path_string,
475                                      EmpathyContactListView         *view)
476 {
477         EmpathyContactListViewPriv *priv = GET_PRIV (view);
478         GtkTreeModel               *model;
479         GtkTreeIter                 iter;
480         EmpathyContact             *contact;
481
482         if (!(priv->contact_features & EMPATHY_CONTACT_FEATURE_CALL)) {
483                 return;
484         }
485
486         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
487         if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string)) {
488                 return;
489         }
490
491         gtk_tree_model_get (model, &iter,
492                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
493                             -1);
494
495         if (contact) {
496                 empathy_call_with_contact (contact);
497                 g_object_unref (contact);
498         }
499 }
500
501 static void
502 contact_list_view_cell_set_background (EmpathyContactListView *view,
503                                        GtkCellRenderer       *cell,
504                                        gboolean               is_group,
505                                        gboolean               is_active)
506 {
507         GdkColor  color;
508         GtkStyle *style;
509
510         style = gtk_widget_get_style (GTK_WIDGET (view));
511
512         if (!is_group && is_active) {
513                 color = style->bg[GTK_STATE_SELECTED];
514
515                 /* Here we take the current theme colour and add it to
516                  * the colour for white and average the two. This
517                  * gives a colour which is inline with the theme but
518                  * slightly whiter.
519                  */
520                 color.red = (color.red + (style->white).red) / 2;
521                 color.green = (color.green + (style->white).green) / 2;
522                 color.blue = (color.blue + (style->white).blue) / 2;
523
524                 g_object_set (cell,
525                               "cell-background-gdk", &color,
526                               NULL);
527         } else {
528                 g_object_set (cell,
529                               "cell-background-gdk", NULL,
530                               NULL);
531         }
532 }
533
534 static void
535 contact_list_view_pixbuf_cell_data_func (GtkTreeViewColumn     *tree_column,
536                                          GtkCellRenderer       *cell,
537                                          GtkTreeModel          *model,
538                                          GtkTreeIter           *iter,
539                                          EmpathyContactListView *view)
540 {
541         gchar    *icon_name;
542         gboolean  is_group;
543         gboolean  is_active;
544
545         gtk_tree_model_get (model, iter,
546                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
547                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
548                             EMPATHY_CONTACT_LIST_STORE_COL_ICON_STATUS, &icon_name,
549                             -1);
550
551         g_object_set (cell,
552                       "visible", !is_group,
553                       "icon-name", icon_name,
554                       NULL);
555
556         g_free (icon_name);
557
558         contact_list_view_cell_set_background (view, cell, is_group, is_active);
559 }
560
561 static void
562 contact_list_view_voip_cell_data_func (GtkTreeViewColumn      *tree_column,
563                                        GtkCellRenderer        *cell,
564                                        GtkTreeModel           *model,
565                                        GtkTreeIter            *iter,
566                                        EmpathyContactListView *view)
567 {
568         gboolean is_group;
569         gboolean is_active;
570         gboolean can_voip;
571
572         gtk_tree_model_get (model, iter,
573                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
574                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
575                             EMPATHY_CONTACT_LIST_STORE_COL_CAN_VOIP, &can_voip,
576                             -1);
577
578         g_object_set (cell,
579                       "visible", !is_group && can_voip,
580                       "icon-name", EMPATHY_IMAGE_VOIP,
581                       NULL);
582
583         contact_list_view_cell_set_background (view, cell, is_group, is_active);
584 }
585
586 static void
587 contact_list_view_avatar_cell_data_func (GtkTreeViewColumn     *tree_column,
588                                          GtkCellRenderer       *cell,
589                                          GtkTreeModel          *model,
590                                          GtkTreeIter           *iter,
591                                          EmpathyContactListView *view)
592 {
593         GdkPixbuf *pixbuf;
594         gboolean   show_avatar;
595         gboolean   is_group;
596         gboolean   is_active;
597
598         gtk_tree_model_get (model, iter,
599                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR, &pixbuf,
600                             EMPATHY_CONTACT_LIST_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
601                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
602                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
603                             -1);
604
605         g_object_set (cell,
606                       "visible", !is_group && show_avatar,
607                       "pixbuf", pixbuf,
608                       NULL);
609
610         if (pixbuf) {
611                 g_object_unref (pixbuf);
612         }
613
614         contact_list_view_cell_set_background (view, cell, is_group, is_active);
615 }
616
617 static void
618 contact_list_view_text_cell_data_func (GtkTreeViewColumn     *tree_column,
619                                        GtkCellRenderer       *cell,
620                                        GtkTreeModel          *model,
621                                        GtkTreeIter           *iter,
622                                        EmpathyContactListView *view)
623 {
624         gboolean is_group;
625         gboolean is_active;
626         gboolean show_status;
627
628         gtk_tree_model_get (model, iter,
629                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
630                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
631                             EMPATHY_CONTACT_LIST_STORE_COL_STATUS_VISIBLE, &show_status,
632                             -1);
633
634         g_object_set (cell,
635                       "show-status", show_status,
636                       NULL);
637
638         contact_list_view_cell_set_background (view, cell, is_group, is_active);
639 }
640
641 static void
642 contact_list_view_expander_cell_data_func (GtkTreeViewColumn     *column,
643                                            GtkCellRenderer       *cell,
644                                            GtkTreeModel          *model,
645                                            GtkTreeIter           *iter,
646                                            EmpathyContactListView *view)
647 {
648         gboolean is_group;
649         gboolean is_active;
650
651         gtk_tree_model_get (model, iter,
652                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
653                             EMPATHY_CONTACT_LIST_STORE_COL_IS_ACTIVE, &is_active,
654                             -1);
655
656         if (gtk_tree_model_iter_has_child (model, iter)) {
657                 GtkTreePath *path;
658                 gboolean     row_expanded;
659
660                 path = gtk_tree_model_get_path (model, iter);
661                 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (column->tree_view), path);
662                 gtk_tree_path_free (path);
663
664                 g_object_set (cell,
665                               "visible", TRUE,
666                               "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
667                               NULL);
668         } else {
669                 g_object_set (cell, "visible", FALSE, NULL);
670         }
671
672         contact_list_view_cell_set_background (view, cell, is_group, is_active);
673 }
674
675 static void
676 contact_list_view_row_expand_or_collapse_cb (EmpathyContactListView *view,
677                                              GtkTreeIter           *iter,
678                                              GtkTreePath           *path,
679                                              gpointer               user_data)
680 {
681         EmpathyContactListViewPriv *priv = GET_PRIV (view);
682         GtkTreeModel               *model;
683         gchar                      *name;
684         gboolean                    expanded;
685
686         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE)) {
687                 return;
688         }
689
690         model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
691
692         gtk_tree_model_get (model, iter,
693                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
694                             -1);
695
696         expanded = GPOINTER_TO_INT (user_data);
697         empathy_contact_group_set_expanded (name, expanded);
698
699         g_free (name);
700 }
701
702 static void
703 contact_list_view_row_has_child_toggled_cb (GtkTreeModel          *model,
704                                             GtkTreePath           *path,
705                                             GtkTreeIter           *iter,
706                                             EmpathyContactListView *view)
707 {
708         EmpathyContactListViewPriv *priv = GET_PRIV (view);
709         gboolean  is_group = FALSE;
710         gchar    *name = NULL;
711
712         gtk_tree_model_get (model, iter,
713                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
714                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
715                             -1);
716
717         if (!is_group || G_STR_EMPTY (name)) {
718                 g_free (name);
719                 return;
720         }
721
722         if (!(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_SAVE) ||
723             empathy_contact_group_get_expanded (name)) {
724                 g_signal_handlers_block_by_func (view,
725                                                  contact_list_view_row_expand_or_collapse_cb,
726                                                  GINT_TO_POINTER (TRUE));
727                 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
728                 g_signal_handlers_unblock_by_func (view,
729                                                    contact_list_view_row_expand_or_collapse_cb,
730                                                    GINT_TO_POINTER (TRUE));
731         } else {
732                 g_signal_handlers_block_by_func (view,
733                                                  contact_list_view_row_expand_or_collapse_cb,
734                                                  GINT_TO_POINTER (FALSE));
735                 gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
736                 g_signal_handlers_unblock_by_func (view,
737                                                    contact_list_view_row_expand_or_collapse_cb,
738                                                    GINT_TO_POINTER (FALSE));
739         }
740
741         g_free (name);
742 }
743
744 static void
745 contact_list_view_setup (EmpathyContactListView *view)
746 {
747         EmpathyContactListViewPriv *priv;
748         GtkCellRenderer           *cell;
749         GtkTreeViewColumn         *col;
750         gint                       i;
751
752         priv = GET_PRIV (view);
753
754         gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view),
755                                              empathy_contact_list_store_search_equal_func,
756                                              NULL, NULL);
757
758         g_signal_connect (priv->store, "row-has-child-toggled",
759                           G_CALLBACK (contact_list_view_row_has_child_toggled_cb),
760                           view);
761         gtk_tree_view_set_model (GTK_TREE_VIEW (view),
762                                  GTK_TREE_MODEL (priv->store));
763
764         /* Setup view */
765         g_object_set (view,
766                       "headers-visible", FALSE,
767                       "reorderable", TRUE,
768                       "show-expanders", FALSE,
769                       NULL);
770
771         col = gtk_tree_view_column_new ();
772
773         /* State */
774         cell = gtk_cell_renderer_pixbuf_new ();
775         gtk_tree_view_column_pack_start (col, cell, FALSE);
776         gtk_tree_view_column_set_cell_data_func (
777                 col, cell,
778                 (GtkTreeCellDataFunc) contact_list_view_pixbuf_cell_data_func,
779                 view, NULL);
780
781         g_object_set (cell,
782                       "xpad", 5,
783                       "ypad", 1,
784                       "visible", FALSE,
785                       NULL);
786
787         /* Name */
788         cell = empathy_cell_renderer_text_new ();
789         gtk_tree_view_column_pack_start (col, cell, TRUE);
790         gtk_tree_view_column_set_cell_data_func (
791                 col, cell,
792                 (GtkTreeCellDataFunc) contact_list_view_text_cell_data_func,
793                 view, NULL);
794
795         gtk_tree_view_column_add_attribute (col, cell,
796                                             "name", EMPATHY_CONTACT_LIST_STORE_COL_NAME);
797         gtk_tree_view_column_add_attribute (col, cell,
798                                             "status", EMPATHY_CONTACT_LIST_STORE_COL_STATUS);
799         gtk_tree_view_column_add_attribute (col, cell,
800                                             "is_group", EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP);
801
802         /* Voip Capability Icon */
803         cell = empathy_cell_renderer_activatable_new ();
804         gtk_tree_view_column_pack_start (col, cell, FALSE);
805         gtk_tree_view_column_set_cell_data_func (
806                 col, cell,
807                 (GtkTreeCellDataFunc) contact_list_view_voip_cell_data_func,
808                 view, NULL);
809
810         g_object_set (cell,
811                       "visible", FALSE,
812                       NULL);
813
814         g_signal_connect (cell, "path-activated",
815                           G_CALLBACK (contact_list_view_voip_activated_cb),
816                           view);
817
818         /* Avatar */
819         cell = gtk_cell_renderer_pixbuf_new ();
820         gtk_tree_view_column_pack_start (col, cell, FALSE);
821         gtk_tree_view_column_set_cell_data_func (
822                 col, cell,
823                 (GtkTreeCellDataFunc) contact_list_view_avatar_cell_data_func,
824                 view, NULL);
825
826         g_object_set (cell,
827                       "xpad", 0,
828                       "ypad", 0,
829                       "visible", FALSE,
830                       "width", 32,
831                       "height", 32,
832                       NULL);
833
834         /* Expander */
835         cell = empathy_cell_renderer_expander_new ();
836         gtk_tree_view_column_pack_end (col, cell, FALSE);
837         gtk_tree_view_column_set_cell_data_func (
838                 col, cell,
839                 (GtkTreeCellDataFunc) contact_list_view_expander_cell_data_func,
840                 view, NULL);
841
842         /* Actually add the column now we have added all cell renderers */
843         gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
844
845         /* Drag & Drop. */
846         for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i) {
847                 drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target,
848                                                       FALSE);
849         }
850
851         for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i) {
852                 drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
853                                                         FALSE);
854         }
855 }
856
857 static void
858 contact_list_view_set_list_features (EmpathyContactListView         *view,
859                                      EmpathyContactListFeatureFlags  features)
860 {
861         EmpathyContactListViewPriv *priv = GET_PRIV (view);
862
863         g_return_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view));
864
865         priv->list_features = features;
866
867         /* Update DnD source/dest */
868         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DRAG) {
869                 gtk_drag_source_set (GTK_WIDGET (view),
870                                      GDK_BUTTON1_MASK,
871                                      drag_types_source,
872                                      G_N_ELEMENTS (drag_types_source),
873                                      GDK_ACTION_MOVE | GDK_ACTION_COPY);
874         } else {
875                 gtk_drag_source_unset (GTK_WIDGET (view));
876
877         }
878
879         if (features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_DROP) {
880                 gtk_drag_dest_set (GTK_WIDGET (view),
881                                    GTK_DEST_DEFAULT_ALL,
882                                    drag_types_dest,
883                                    G_N_ELEMENTS (drag_types_dest),
884                                    GDK_ACTION_MOVE | GDK_ACTION_COPY);
885         } else {
886                 /* FIXME: URI could still be  droped depending on FT feature */
887                 gtk_drag_dest_unset (GTK_WIDGET (view));
888         }
889 }
890
891 static void
892 contact_list_view_finalize (GObject *object)
893 {
894         EmpathyContactListViewPriv *priv;
895
896         priv = GET_PRIV (object);
897
898         if (priv->store) {
899                 g_object_unref (priv->store);
900         }
901
902         G_OBJECT_CLASS (empathy_contact_list_view_parent_class)->finalize (object);
903 }
904
905 static void
906 contact_list_view_get_property (GObject    *object,
907                                 guint       param_id,
908                                 GValue     *value,
909                                 GParamSpec *pspec)
910 {
911         EmpathyContactListViewPriv *priv;
912
913         priv = GET_PRIV (object);
914
915         switch (param_id) {
916         case PROP_STORE:
917                 g_value_set_object (value, priv->store);
918                 break;
919         case PROP_LIST_FEATURES:
920                 g_value_set_flags (value, priv->list_features);
921                 break;
922         case PROP_CONTACT_FEATURES:
923                 g_value_set_flags (value, priv->contact_features);
924                 break;
925         default:
926                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
927                 break;
928         };
929 }
930
931 static void
932 contact_list_view_set_property (GObject      *object,
933                                 guint         param_id,
934                                 const GValue *value,
935                                 GParamSpec   *pspec)
936 {
937         EmpathyContactListView     *view = EMPATHY_CONTACT_LIST_VIEW (object);
938         EmpathyContactListViewPriv *priv = GET_PRIV (object);
939
940         switch (param_id) {
941         case PROP_STORE:
942                 priv->store = g_value_dup_object (value);
943                 contact_list_view_setup (view);
944                 break;
945         case PROP_LIST_FEATURES:
946                 contact_list_view_set_list_features (view, g_value_get_flags (value));
947                 break;
948         case PROP_CONTACT_FEATURES:
949                 priv->contact_features = g_value_get_flags (value);
950                 break;
951         default:
952                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
953                 break;
954         };
955 }
956
957 static void
958 empathy_contact_list_view_class_init (EmpathyContactListViewClass *klass)
959 {
960         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
961         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
962
963         object_class->finalize = contact_list_view_finalize;
964         object_class->get_property = contact_list_view_get_property;
965         object_class->set_property = contact_list_view_set_property;
966
967         widget_class->drag_data_received = contact_list_view_drag_data_received;
968         widget_class->drag_drop          = contact_list_view_drag_drop;
969         widget_class->drag_begin         = contact_list_view_drag_begin;
970         widget_class->drag_data_get      = contact_list_view_drag_data_get;
971         widget_class->drag_end           = contact_list_view_drag_end;
972         /* FIXME: noticed but when you drag the row over the treeview
973          * fast, it seems to stop redrawing itself, if we don't
974          * connect this signal, all is fine.
975          */
976         widget_class->drag_motion        = contact_list_view_drag_motion;
977
978         signals[DRAG_CONTACT_RECEIVED] =
979                 g_signal_new ("drag-contact-received",
980                               G_OBJECT_CLASS_TYPE (klass),
981                               G_SIGNAL_RUN_LAST,
982                               0,
983                               NULL, NULL,
984                               _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
985                               G_TYPE_NONE,
986                               3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
987
988         g_object_class_install_property (object_class,
989                                          PROP_STORE,
990                                          g_param_spec_object ("store",
991                                                              "The store of the view",
992                                                              "The store of the view",
993                                                               EMPATHY_TYPE_CONTACT_LIST_STORE,
994                                                               G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
995         g_object_class_install_property (object_class,
996                                          PROP_LIST_FEATURES,
997                                          g_param_spec_flags ("list-features",
998                                                              "Features of the view",
999                                                              "Falgs for all enabled features",
1000                                                               EMPATHY_TYPE_CONTACT_LIST_FEATURE_FLAGS,
1001                                                               EMPATHY_CONTACT_LIST_FEATURE_NONE,
1002                                                               G_PARAM_READWRITE));
1003         g_object_class_install_property (object_class,
1004                                          PROP_CONTACT_FEATURES,
1005                                          g_param_spec_flags ("contact-features",
1006                                                              "Features of the contact menu",
1007                                                              "Falgs for all enabled features for the menu",
1008                                                               EMPATHY_TYPE_CONTACT_FEATURE_FLAGS,
1009                                                               EMPATHY_CONTACT_FEATURE_NONE,
1010                                                               G_PARAM_READWRITE));
1011
1012         g_type_class_add_private (object_class, sizeof (EmpathyContactListViewPriv));
1013 }
1014
1015 static void
1016 empathy_contact_list_view_init (EmpathyContactListView *view)
1017 {
1018         /* Get saved group states. */
1019         empathy_contact_groups_get_all ();
1020
1021         gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view), 
1022                                               empathy_contact_list_store_row_separator_func,
1023                                               NULL, NULL);
1024
1025         /* Connect to tree view signals rather than override. */
1026         g_signal_connect (view,
1027                           "button-press-event",
1028                           G_CALLBACK (contact_list_view_button_press_event_cb),
1029                           NULL);
1030         g_signal_connect (view,
1031                           "row-activated",
1032                           G_CALLBACK (contact_list_view_row_activated_cb),
1033                           NULL);
1034         g_signal_connect (view,
1035                           "row-expanded",
1036                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1037                           GINT_TO_POINTER (TRUE));
1038         g_signal_connect (view,
1039                           "row-collapsed",
1040                           G_CALLBACK (contact_list_view_row_expand_or_collapse_cb),
1041                           GINT_TO_POINTER (FALSE));
1042 }
1043
1044 EmpathyContactListView *
1045 empathy_contact_list_view_new (EmpathyContactListStore        *store,
1046                                EmpathyContactListFeatureFlags  list_features,
1047                                EmpathyContactFeatureFlags      contact_features)
1048 {
1049         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_STORE (store), NULL);
1050         
1051         return g_object_new (EMPATHY_TYPE_CONTACT_LIST_VIEW,
1052                              "store", store,
1053                              "contact-features", contact_features,
1054                              "list-features", list_features,
1055                              NULL);
1056 }
1057
1058 EmpathyContact *
1059 empathy_contact_list_view_get_selected (EmpathyContactListView *view)
1060 {
1061         EmpathyContactListViewPriv *priv;
1062         GtkTreeSelection          *selection;
1063         GtkTreeIter                iter;
1064         GtkTreeModel              *model;
1065         EmpathyContact             *contact;
1066
1067         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1068
1069         priv = GET_PRIV (view);
1070
1071         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1072         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1073                 return NULL;
1074         }
1075
1076         gtk_tree_model_get (model, &iter,
1077                             EMPATHY_CONTACT_LIST_STORE_COL_CONTACT, &contact,
1078                             -1);
1079
1080         return contact;
1081 }
1082
1083 gchar *
1084 empathy_contact_list_view_get_selected_group (EmpathyContactListView *view)
1085 {
1086         EmpathyContactListViewPriv *priv;
1087         GtkTreeSelection          *selection;
1088         GtkTreeIter                iter;
1089         GtkTreeModel              *model;
1090         gboolean                   is_group;
1091         gchar                     *name;
1092
1093         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1094
1095         priv = GET_PRIV (view);
1096
1097         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1098         if (!gtk_tree_selection_get_selected (selection, &model, &iter)) {
1099                 return NULL;
1100         }
1101
1102         gtk_tree_model_get (model, &iter,
1103                             EMPATHY_CONTACT_LIST_STORE_COL_IS_GROUP, &is_group,
1104                             EMPATHY_CONTACT_LIST_STORE_COL_NAME, &name,
1105                             -1);
1106
1107         if (!is_group) {
1108                 g_free (name);
1109                 return NULL;
1110         }
1111
1112         return name;
1113 }
1114
1115 static gboolean
1116 contact_list_view_remove_dialog_show (GtkWindow   *parent, 
1117                                       const gchar *window_title, 
1118                                       const gchar *text)
1119 {
1120         GtkWidget *dialog, *label, *image, *hbox;
1121         gboolean res;
1122         
1123         dialog = gtk_dialog_new_with_buttons (window_title, parent,
1124                                               GTK_DIALOG_MODAL,
1125                                               GTK_STOCK_DELETE, GTK_RESPONSE_YES,
1126                                               GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1127                                               NULL);
1128         gtk_dialog_set_has_separator (GTK_DIALOG(dialog), FALSE);
1129          
1130         label = gtk_label_new (text);
1131         image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1132          
1133         hbox = gtk_hbox_new (FALSE, 5);
1134         gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
1135         gtk_box_pack_start_defaults (GTK_BOX (hbox), image);
1136         gtk_box_pack_start_defaults (GTK_BOX (hbox), label);     
1137         gtk_box_pack_start_defaults (GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox);
1138
1139         gtk_widget_show (image);
1140         gtk_widget_show (label);
1141         gtk_widget_show (hbox);
1142         gtk_widget_show (dialog);
1143          
1144         res = gtk_dialog_run (GTK_DIALOG (dialog));
1145         gtk_widget_destroy (dialog);
1146
1147         return (res == GTK_RESPONSE_YES);
1148 }
1149
1150 static void
1151 contact_list_view_group_remove_activate_cb (GtkMenuItem            *menuitem,
1152                                             EmpathyContactListView *view)
1153 {
1154         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1155         gchar                      *group;
1156
1157         group = empathy_contact_list_view_get_selected_group (view);
1158         if (group) {
1159                 gchar     *text;
1160                 GtkWindow *parent;
1161
1162                 text = g_strdup_printf (_("Do you really want to remove the group '%s' ?"), group);
1163                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1164                 if (contact_list_view_remove_dialog_show (parent, _("Removing group"), text)) {
1165                         EmpathyContactList *list;
1166
1167                         list = empathy_contact_list_store_get_list_iface (priv->store);
1168                         empathy_contact_list_remove_group (list, group);
1169                 }
1170
1171                 g_free (text);
1172         }
1173
1174         g_free (group);
1175 }
1176
1177 GtkWidget *
1178 empathy_contact_list_view_get_group_menu (EmpathyContactListView *view)
1179 {
1180         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1181         gchar                      *group;
1182         GtkWidget                  *menu;
1183         GtkWidget                  *item;
1184         GtkWidget                  *image;
1185
1186         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1187
1188         if (!(priv->list_features & (EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME |
1189                                      EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE))) {
1190                 return NULL;
1191         }
1192
1193         group = empathy_contact_list_view_get_selected_group (view);
1194         if (!group) {
1195                 return NULL;
1196         }
1197
1198         menu = gtk_menu_new ();
1199
1200         /* FIXME: Not implemented yet
1201         if (priv->features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_RENAME) {
1202                 item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1203                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1204                 gtk_widget_show (item);
1205                 g_signal_connect (item, "activate",
1206                                   G_CALLBACK (contact_list_view_group_rename_activate_cb),
1207                                   view);
1208         }*/
1209
1210         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_GROUPS_REMOVE) {
1211                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1212                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1213                                                       GTK_ICON_SIZE_MENU);
1214                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1215                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1216                 gtk_widget_show (item);
1217                 g_signal_connect (item, "activate",
1218                                   G_CALLBACK (contact_list_view_group_remove_activate_cb),
1219                                   view);
1220         }
1221
1222         g_free (group);
1223
1224         return menu;
1225 }
1226
1227 static void
1228 contact_list_view_remove_activate_cb (GtkMenuItem            *menuitem,
1229                                       EmpathyContactListView *view)
1230 {
1231         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1232         EmpathyContact             *contact;
1233                 
1234         contact = empathy_contact_list_view_get_selected (view);
1235
1236         if (contact) {
1237                 gchar     *text; 
1238                 GtkWindow *parent;
1239
1240                 parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1241                 text = g_strdup_printf (_("Do you really want to remove the contact '%s' ?"),
1242                                         empathy_contact_get_name (contact));                                            
1243                 if (contact_list_view_remove_dialog_show (parent, _("Removing contact"), text)) {
1244                         EmpathyContactList *list;
1245
1246                         list = empathy_contact_list_store_get_list_iface (priv->store);
1247                         empathy_contact_list_remove (list, contact, 
1248                                 _("Sorry, I don't want you in my contact list anymore."));
1249                 }
1250
1251                 g_free (text);
1252                 g_object_unref (contact);
1253         }
1254 }
1255
1256 GtkWidget *
1257 empathy_contact_list_view_get_contact_menu (EmpathyContactListView *view)
1258 {
1259         EmpathyContactListViewPriv *priv = GET_PRIV (view);
1260         EmpathyContact             *contact;
1261         GtkWidget                  *menu;
1262         GtkWidget                  *item;
1263         GtkWidget                  *image;
1264
1265         g_return_val_if_fail (EMPATHY_IS_CONTACT_LIST_VIEW (view), NULL);
1266
1267         contact = empathy_contact_list_view_get_selected (view);
1268         if (!contact) {
1269                 return NULL;
1270         }
1271
1272         menu = empathy_contact_menu_new (contact, priv->contact_features);
1273
1274         if (!menu &&
1275             !(priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE)) {
1276                 g_object_unref (contact);
1277                 return NULL;
1278         }
1279
1280         if (menu) {
1281                 /* Separator */
1282                 item = gtk_separator_menu_item_new ();
1283                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1284                 gtk_widget_show (item);
1285         } else {
1286                 menu = gtk_menu_new ();
1287         }
1288
1289         /* Remove contact */
1290         if (priv->list_features & EMPATHY_CONTACT_LIST_FEATURE_CONTACT_REMOVE) {
1291                 item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1292                 image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1293                                                       GTK_ICON_SIZE_MENU);
1294                 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1295                 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1296                 gtk_widget_show (item);
1297                 g_signal_connect (item, "activate",
1298                                   G_CALLBACK (contact_list_view_remove_activate_cb),
1299                                   view);
1300         }
1301
1302         g_object_unref (contact);
1303
1304         return menu;
1305 }
1306