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