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