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