]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-view.c
Don't enclose trivial blocks in {}.
[empathy.git] / libempathy-gtk / empathy-individual-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-2010 Collabora Ltd.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public
17  * License along with this program; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA  02110-1301  USA
20  *
21  * Authors: Mikael Hallendal <micke@imendio.com>
22  *          Martyn Russell <martyn@imendio.com>
23  *          Xavier Claessens <xclaesse@gmail.com>
24  *          Travis Reitter <travis.reitter@collabora.co.uk>
25  */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include <glib/gi18n-lib.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <gtk/gtk.h>
34
35 #include <folks/folks.h>
36 #include <telepathy-glib/account-manager.h>
37 #include <telepathy-glib/util.h>
38
39 #include <libempathy/empathy-call-factory.h>
40 #include <libempathy/empathy-individual-manager.h>
41 #include <libempathy/empathy-contact-groups.h>
42 #include <libempathy/empathy-dispatcher.h>
43 #include <libempathy/empathy-utils.h>
44
45 #include "empathy-individual-view.h"
46 #include "empathy-individual-menu.h"
47 #include "empathy-individual-store.h"
48 #include "empathy-images.h"
49 #include "empathy-cell-renderer-expander.h"
50 #include "empathy-cell-renderer-text.h"
51 #include "empathy-cell-renderer-activatable.h"
52 #include "empathy-ui-utils.h"
53 #include "empathy-gtk-enum-types.h"
54 #include "empathy-gtk-marshal.h"
55
56 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
57 #include <libempathy/empathy-debug.h>
58
59 /* Active users are those which have recently changed state
60  * (e.g. online, offline or from normal to a busy state).
61  */
62
63 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualView)
64 typedef struct
65 {
66   EmpathyIndividualStore *store;
67   GtkTreeRowReference *drag_row;
68   EmpathyIndividualViewFeatureFlags view_features;
69   EmpathyContactFeatureFlags individual_features;
70   GtkWidget *tooltip_widget;
71   GtkTargetList *file_targets;
72
73   GtkTreeModelFilter *filter;
74   GtkWidget *search_widget;
75 } EmpathyIndividualViewPriv;
76
77 typedef struct
78 {
79   EmpathyIndividualView *view;
80   GtkTreePath *path;
81   guint timeout_id;
82 } DragMotionData;
83
84 typedef struct
85 {
86   EmpathyIndividualView *view;
87   FolksIndividual *individual;
88   gboolean remove;
89 } ShowActiveData;
90
91 enum
92 {
93   PROP_0,
94   PROP_STORE,
95   PROP_VIEW_FEATURES,
96   PROP_INDIVIDUAL_FEATURES,
97 };
98
99 /* TODO: re-add DRAG_TYPE_CONTACT_ID, for the case that we're dragging around
100  * specific EmpathyContacts (between/in/out of Individuals) */
101 enum DndDragType
102 {
103   DND_DRAG_TYPE_INDIVIDUAL_ID,
104   DND_DRAG_TYPE_URI_LIST,
105   DND_DRAG_TYPE_STRING,
106 };
107
108 static const GtkTargetEntry drag_types_dest[] = {
109   {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
110   {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
111   {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
112   {"text/plain", 0, DND_DRAG_TYPE_STRING},
113   {"STRING", 0, DND_DRAG_TYPE_STRING},
114 };
115
116 static const GtkTargetEntry drag_types_dest_file[] = {
117   {"text/path-list", 0, DND_DRAG_TYPE_URI_LIST},
118   {"text/uri-list", 0, DND_DRAG_TYPE_URI_LIST},
119 };
120
121 static const GtkTargetEntry drag_types_source[] = {
122   {"text/contact-id", 0, DND_DRAG_TYPE_INDIVIDUAL_ID},
123 };
124
125 static GdkAtom drag_atoms_dest[G_N_ELEMENTS (drag_types_dest)];
126 static GdkAtom drag_atoms_source[G_N_ELEMENTS (drag_types_source)];
127
128 enum
129 {
130   DRAG_CONTACT_RECEIVED,
131   LAST_SIGNAL
132 };
133
134 static guint signals[LAST_SIGNAL];
135
136 G_DEFINE_TYPE (EmpathyIndividualView, empathy_individual_view,
137     GTK_TYPE_TREE_VIEW);
138
139 static gboolean
140 individual_view_is_visible_individual (EmpathyIndividualView *self,
141     FolksIndividual *individual)
142 {
143   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
144   EmpathyLiveSearch *live = EMPATHY_LIVE_SEARCH (priv->search_widget);
145   const gchar *str;
146   const gchar *p;
147   gchar *dup_str = NULL;
148   gboolean visible;
149   GList *personas, *l;
150
151   g_assert (live != NULL);
152
153   /* check alias name */
154   str = folks_individual_get_alias (individual);
155   if (empathy_live_search_match (live, str))
156     return TRUE;
157
158   /* check contact id, remove the @server.com part */
159   personas = folks_individual_get_personas (individual);
160   for (l = personas; l; l = l->next)
161     {
162       str = folks_persona_get_uid (l->data);
163       p = strstr (str, "@");
164       if (p != NULL)
165         str = dup_str = g_strndup (str, p - str);
166
167       visible = empathy_live_search_match (live, str);
168       g_free (dup_str);
169       if (visible)
170         return TRUE;
171     }
172
173   /* FIXME: Add more rules here, we could check phone numbers in
174    * contact's vCard for example. */
175
176   return FALSE;
177 }
178
179 static gboolean
180 individual_view_filter_visible_func (GtkTreeModel *model,
181     GtkTreeIter *iter,
182     gpointer user_data)
183 {
184   EmpathyIndividualView *self = EMPATHY_INDIVIDUAL_VIEW (user_data);
185   EmpathyIndividualViewPriv *priv = GET_PRIV (self);
186   FolksIndividual *individual = NULL;
187   gboolean is_group, is_separator, valid;
188   GtkTreeIter child_iter;
189   gboolean visible;
190
191   if (priv->search_widget == NULL ||
192       !gtk_widget_get_visible (priv->search_widget))
193     return TRUE;
194
195   gtk_tree_model_get (model, iter,
196       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
197       EMPATHY_INDIVIDUAL_STORE_COL_IS_SEPARATOR, &is_separator,
198       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
199       -1);
200
201   if (individual != NULL)
202     {
203       visible = individual_view_is_visible_individual (self, individual);
204       g_object_unref (individual);
205       return visible;
206     }
207
208   if (is_separator)
209     return TRUE;
210
211   /* Not a contact, not a separator, must be a group */
212   g_return_val_if_fail (is_group, FALSE);
213
214   /* only show groups which are not empty */
215   for (valid = gtk_tree_model_iter_children (model, &child_iter, iter);
216        valid; valid = gtk_tree_model_iter_next (model, &child_iter))
217     {
218       gtk_tree_model_get (model, &child_iter,
219         EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
220         -1);
221
222       if (individual == NULL)
223         continue;
224
225       visible = individual_view_is_visible_individual (self, individual);
226       g_object_unref (individual);
227
228       /* show group if it has at least one visible contact in it */
229       if (visible)
230         return TRUE;
231     }
232
233   return FALSE;
234 }
235
236 static void
237 individual_view_tooltip_destroy_cb (GtkWidget *widget,
238     EmpathyIndividualView *view)
239 {
240   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
241
242   if (priv->tooltip_widget != NULL)
243     {
244       DEBUG ("Tooltip destroyed");
245       g_object_unref (priv->tooltip_widget);
246       priv->tooltip_widget = NULL;
247     }
248 }
249
250 static gboolean
251 individual_view_query_tooltip_cb (EmpathyIndividualView *view,
252     gint x,
253     gint y,
254     gboolean keyboard_mode,
255     GtkTooltip *tooltip,
256     gpointer user_data)
257 {
258   EmpathyIndividualViewPriv *priv;
259   FolksIndividual *individual;
260   GtkTreeModel *model;
261   GtkTreeIter iter;
262   GtkTreePath *path;
263   static gint running = 0;
264   gboolean ret = FALSE;
265   EmpathyContact *contact;
266
267   priv = GET_PRIV (view);
268
269   /* Avoid an infinite loop. See GNOME bug #574377 */
270   if (running > 0)
271     return FALSE;
272
273   running++;
274
275   /* Don't show the tooltip if there's already a popup menu */
276   if (gtk_menu_get_for_attach_widget (GTK_WIDGET (view)) != NULL)
277     goto OUT;
278
279   if (!gtk_tree_view_get_tooltip_context (GTK_TREE_VIEW (view), &x, &y,
280           keyboard_mode, &model, &path, &iter))
281     goto OUT;
282
283   gtk_tree_view_set_tooltip_row (GTK_TREE_VIEW (view), tooltip, path);
284   gtk_tree_path_free (path);
285
286   gtk_tree_model_get (model, &iter,
287       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual,
288       -1);
289   if (individual == NULL)
290     goto OUT;
291
292   contact = empathy_contact_dup_from_folks_individual (individual);
293   g_object_unref (individual);
294
295   if (contact == NULL)
296     goto OUT;
297
298   if (priv->tooltip_widget == NULL)
299     {
300       priv->tooltip_widget = empathy_contact_widget_new (contact,
301           EMPATHY_CONTACT_WIDGET_FOR_TOOLTIP |
302           EMPATHY_CONTACT_WIDGET_SHOW_LOCATION);
303       gtk_container_set_border_width (GTK_CONTAINER (priv->tooltip_widget), 8);
304       g_object_ref (priv->tooltip_widget);
305       g_signal_connect (priv->tooltip_widget, "destroy",
306           G_CALLBACK (individual_view_tooltip_destroy_cb), view);
307       gtk_widget_show (priv->tooltip_widget);
308     }
309   else
310     empathy_contact_widget_set_contact (priv->tooltip_widget, contact);
311
312   gtk_tooltip_set_custom (tooltip, priv->tooltip_widget);
313   ret = TRUE;
314
315   g_object_unref (contact);
316 OUT:
317   running--;
318
319   return ret;
320 }
321
322 static void
323 individual_view_handle_drag (EmpathyIndividualView *self,
324     FolksIndividual *individual,
325     const gchar *old_group,
326     const gchar *new_group,
327     GdkDragAction action)
328 {
329   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (self));
330   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
331
332   DEBUG ("individual %s dragged from '%s' to '%s'",
333       folks_individual_get_id (individual), old_group, new_group);
334
335   if (!tp_strdiff (new_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
336     {
337       /* Mark contact as favourite */
338       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), TRUE);
339       return;
340     }
341
342   if (!tp_strdiff (old_group, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
343     {
344       /* Remove contact as favourite */
345       folks_favourite_set_is_favourite (FOLKS_FAVOURITE (individual), FALSE);
346
347       /* Don't try to remove it */
348       old_group = NULL;
349     }
350
351   if (new_group != NULL)
352     folks_groups_change_group (FOLKS_GROUPS (individual), new_group, TRUE);
353
354   if (old_group != NULL && action == GDK_ACTION_MOVE)
355     folks_groups_change_group (FOLKS_GROUPS (individual), old_group, FALSE);
356 }
357
358 static gboolean
359 group_can_be_modified (const gchar *name,
360     gboolean is_fake_group,
361     gboolean adding)
362 {
363   /* Real groups can always be modified */
364   if (!is_fake_group)
365     return TRUE;
366
367   /* The favorite fake group can be modified so users can
368    * add/remove favorites using DnD */
369   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
370     return TRUE;
371
372   /* We can remove contacts from the 'ungrouped' fake group */
373   if (!adding && !tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_UNGROUPED))
374     return TRUE;
375
376   return FALSE;
377 }
378
379 static gboolean
380 individual_view_contact_drag_received (GtkWidget *self,
381     GdkDragContext *context,
382     GtkTreeModel *model,
383     GtkTreePath *path,
384     GtkSelectionData *selection)
385 {
386   EmpathyIndividualViewPriv *priv;
387   EmpathyIndividualManager *manager;
388   FolksIndividual *individual;
389   GtkTreePath *source_path;
390   const gchar *sel_data;
391   gchar *new_group = NULL;
392   gchar *old_group = NULL;
393   gboolean new_group_is_fake, old_group_is_fake = TRUE;
394
395   priv = GET_PRIV (self);
396
397   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
398   new_group = empathy_individual_store_get_parent_group (model, path,
399       NULL, &new_group_is_fake);
400
401   if (!group_can_be_modified (new_group, new_group_is_fake, TRUE))
402     return FALSE;
403
404   /* Get source group information. */
405   if (priv->drag_row)
406     {
407       source_path = gtk_tree_row_reference_get_path (priv->drag_row);
408       if (source_path)
409         {
410           old_group =
411               empathy_individual_store_get_parent_group (model, source_path,
412               NULL, &old_group_is_fake);
413           gtk_tree_path_free (source_path);
414         }
415     }
416
417   if (!group_can_be_modified (old_group, old_group_is_fake, FALSE))
418     return FALSE;
419
420   if (!tp_strdiff (old_group, new_group))
421     {
422       g_free (new_group);
423       g_free (old_group);
424       return FALSE;
425     }
426
427   /* XXX: for contacts, we used to ensure the account, create the contact
428    * factory, and then wait on the contacts. But they should already be
429    * created by this point */
430
431   manager = empathy_individual_manager_dup_singleton ();
432   individual = empathy_individual_manager_lookup_member (manager, sel_data);
433
434   if (individual == NULL)
435     {
436       DEBUG ("failed to find drag event individual with ID '%s'", sel_data);
437
438       g_object_unref (manager);
439
440       return FALSE;
441     }
442
443   /* FIXME: We should probably wait for the cb before calling
444    * gtk_drag_finish */
445
446   individual_view_handle_drag (EMPATHY_INDIVIDUAL_VIEW (self), individual,
447       old_group, new_group, gdk_drag_context_get_selected_action (context));
448
449   g_object_unref (G_OBJECT (manager));
450   g_free (old_group);
451   g_free (new_group);
452
453   return TRUE;
454 }
455
456 static gboolean
457 individual_view_file_drag_received (GtkWidget *view,
458     GdkDragContext *context,
459     GtkTreeModel *model,
460     GtkTreePath *path,
461     GtkSelectionData *selection)
462 {
463   GtkTreeIter iter;
464   const gchar *sel_data;
465   FolksIndividual *individual;
466   EmpathyContact *contact;
467
468   sel_data = (const gchar *) gtk_selection_data_get_data (selection);
469
470   gtk_tree_model_get_iter (model, &iter, path);
471   gtk_tree_model_get (model, &iter,
472       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
473   if (individual == NULL)
474     return FALSE;
475
476   contact = empathy_contact_dup_from_folks_individual (individual);
477   empathy_send_file_from_uri_list (contact, sel_data);
478
479   g_object_unref (individual);
480   tp_clear_object (&contact);
481
482   return TRUE;
483 }
484
485 static void
486 individual_view_drag_data_received (GtkWidget *view,
487     GdkDragContext *context,
488     gint x,
489     gint y,
490     GtkSelectionData *selection,
491     guint info,
492     guint time_)
493 {
494   GtkTreeModel *model;
495   gboolean is_row;
496   GtkTreeViewDropPosition position;
497   GtkTreePath *path;
498   gboolean success = TRUE;
499
500   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
501
502   /* Get destination group information. */
503   is_row = gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (view),
504       x, y, &path, &position);
505   if (!is_row)
506     {
507       success = FALSE;
508     }
509   else if (info == DND_DRAG_TYPE_INDIVIDUAL_ID
510       || info == DND_DRAG_TYPE_STRING)
511     {
512       success = individual_view_contact_drag_received (view,
513           context, model, path, selection);
514     }
515   else if (info == DND_DRAG_TYPE_URI_LIST)
516     {
517       success = individual_view_file_drag_received (view,
518           context, model, path, selection);
519     }
520
521   gtk_tree_path_free (path);
522   gtk_drag_finish (context, success, FALSE, GDK_CURRENT_TIME);
523 }
524
525 static gboolean
526 individual_view_drag_motion_cb (DragMotionData *data)
527 {
528   gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), data->path, FALSE);
529
530   data->timeout_id = 0;
531
532   return FALSE;
533 }
534
535 static gboolean
536 individual_view_drag_motion (GtkWidget *widget,
537     GdkDragContext *context,
538     gint x,
539     gint y,
540     guint time_)
541 {
542   EmpathyIndividualViewPriv *priv;
543   GtkTreeModel *model;
544   GdkAtom target;
545   GtkTreeIter iter;
546   static DragMotionData *dm = NULL;
547   GtkTreePath *path;
548   gboolean is_row;
549   gboolean is_different = FALSE;
550   gboolean cleanup = TRUE;
551   gboolean retval = TRUE;
552
553   priv = GET_PRIV (EMPATHY_INDIVIDUAL_VIEW (widget));
554   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
555
556   is_row = gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
557       x, y, &path, NULL, NULL, NULL);
558
559   cleanup &= (dm == NULL);
560
561   if (is_row)
562     {
563       cleanup &= (dm && gtk_tree_path_compare (dm->path, path) != 0);
564       is_different = ((dm == NULL) || ((dm != NULL)
565               && gtk_tree_path_compare (dm->path, path) != 0));
566     }
567   else
568     cleanup &= FALSE;
569
570   if (path == NULL)
571     {
572       /* Coordinates don't point to an actual row, so make sure the pointer
573          and highlighting don't indicate that a drag is possible.
574        */
575       gdk_drag_status (context, GDK_ACTION_DEFAULT, time_);
576       gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
577       return FALSE;
578     }
579   target = gtk_drag_dest_find_target (widget, context, priv->file_targets);
580   gtk_tree_model_get_iter (model, &iter, path);
581
582   if (target == GDK_NONE)
583     {
584       /* If target == GDK_NONE, then we don't have a target that can be
585          dropped on a contact.  This means a contact drag.  If we're
586          pointing to a group, highlight it.  Otherwise, if the contact
587          we're pointing to is in a group, highlight that.  Otherwise,
588          set the drag position to before the first row for a drag into
589          the "non-group" at the top.
590        */
591       GtkTreeIter group_iter;
592       gboolean is_group;
593       GtkTreePath *group_path;
594       gtk_tree_model_get (model, &iter,
595           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
596       if (is_group)
597         {
598           group_iter = iter;
599         }
600       else
601         {
602           if (gtk_tree_model_iter_parent (model, &group_iter, &iter))
603             gtk_tree_model_get (model, &group_iter,
604                 EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group, -1);
605         }
606       if (is_group)
607         {
608           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
609           group_path = gtk_tree_model_get_path (model, &group_iter);
610           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
611               group_path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
612           gtk_tree_path_free (group_path);
613         }
614       else
615         {
616           group_path = gtk_tree_path_new_first ();
617           gdk_drag_status (context, GDK_ACTION_MOVE, time_);
618           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
619               group_path, GTK_TREE_VIEW_DROP_BEFORE);
620         }
621     }
622   else
623     {
624       /* This is a file drag, and it can only be dropped on contacts,
625          not groups.
626        */
627       FolksIndividual *individual;
628       gtk_tree_model_get (model, &iter,
629           EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
630       if (individual != NULL &&
631           folks_individual_is_online (individual) &&
632           (folks_individual_get_capabilities (individual) &
633               FOLKS_CAPABILITIES_FLAGS_FILE_TRANSFER))
634         {
635           gdk_drag_status (context, GDK_ACTION_COPY, time_);
636           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget),
637               path, GTK_TREE_VIEW_DROP_INTO_OR_BEFORE);
638         }
639       else
640         {
641           gdk_drag_status (context, 0, time_);
642           gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (widget), NULL, 0);
643           retval = FALSE;
644         }
645
646       if (individual != NULL)
647         g_object_unref (individual);
648     }
649
650   if (!is_different && !cleanup)
651     return retval;
652
653   if (dm)
654     {
655       gtk_tree_path_free (dm->path);
656       if (dm->timeout_id)
657         {
658           g_source_remove (dm->timeout_id);
659         }
660
661       g_free (dm);
662
663       dm = NULL;
664     }
665
666   if (!gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path))
667     {
668       dm = g_new0 (DragMotionData, 1);
669
670       dm->view = EMPATHY_INDIVIDUAL_VIEW (widget);
671       dm->path = gtk_tree_path_copy (path);
672
673       dm->timeout_id = g_timeout_add_seconds (1,
674           (GSourceFunc) individual_view_drag_motion_cb, dm);
675     }
676
677   return retval;
678 }
679
680 static void
681 individual_view_drag_begin (GtkWidget *widget,
682     GdkDragContext *context)
683 {
684   EmpathyIndividualViewPriv *priv;
685   GtkTreeSelection *selection;
686   GtkTreeModel *model;
687   GtkTreePath *path;
688   GtkTreeIter iter;
689
690   priv = GET_PRIV (widget);
691
692   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_begin (widget,
693       context);
694
695   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
696   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
697     return;
698
699   path = gtk_tree_model_get_path (model, &iter);
700   priv->drag_row = gtk_tree_row_reference_new (model, path);
701   gtk_tree_path_free (path);
702 }
703
704 static void
705 individual_view_drag_data_get (GtkWidget *widget,
706     GdkDragContext *context,
707     GtkSelectionData *selection,
708     guint info,
709     guint time_)
710 {
711   EmpathyIndividualViewPriv *priv;
712   GtkTreePath *src_path;
713   GtkTreeIter iter;
714   GtkTreeModel *model;
715   FolksIndividual *individual;
716   const gchar *individual_id;
717
718   priv = GET_PRIV (widget);
719
720   model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
721   if (priv->drag_row == NULL)
722     return;
723
724   src_path = gtk_tree_row_reference_get_path (priv->drag_row);
725   if (src_path == NULL)
726     return;
727
728   if (!gtk_tree_model_get_iter (model, &iter, src_path))
729     {
730       gtk_tree_path_free (src_path);
731       return;
732     }
733
734   gtk_tree_path_free (src_path);
735
736   individual =
737       empathy_individual_view_dup_selected (EMPATHY_INDIVIDUAL_VIEW (widget));
738   if (individual == NULL)
739     return;
740
741   individual_id = folks_individual_get_id (individual);
742
743   switch (info)
744     {
745     case DND_DRAG_TYPE_INDIVIDUAL_ID:
746       gtk_selection_data_set (selection, drag_atoms_source[info], 8,
747           (guchar *) individual_id, strlen (individual_id) + 1);
748       break;
749     }
750
751   g_object_unref (individual);
752 }
753
754 static void
755 individual_view_drag_end (GtkWidget *widget,
756     GdkDragContext *context)
757 {
758   EmpathyIndividualViewPriv *priv;
759
760   priv = GET_PRIV (widget);
761
762   GTK_WIDGET_CLASS (empathy_individual_view_parent_class)->drag_end (widget,
763       context);
764
765   if (priv->drag_row)
766     {
767       gtk_tree_row_reference_free (priv->drag_row);
768       priv->drag_row = NULL;
769     }
770 }
771
772 static gboolean
773 individual_view_drag_drop (GtkWidget *widget,
774     GdkDragContext *drag_context,
775     gint x,
776     gint y,
777     guint time_)
778 {
779   return FALSE;
780 }
781
782 typedef struct
783 {
784   EmpathyIndividualView *view;
785   guint button;
786   guint32 time;
787 } MenuPopupData;
788
789 static gboolean
790 individual_view_popup_menu_idle_cb (gpointer user_data)
791 {
792   MenuPopupData *data = user_data;
793   GtkWidget *menu;
794
795   menu = empathy_individual_view_get_individual_menu (data->view);
796   if (menu == NULL)
797     menu = empathy_individual_view_get_group_menu (data->view);
798
799   if (menu != NULL)
800     {
801       g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
802       gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (data->view),
803           NULL);
804       gtk_widget_show (menu);
805       gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, data->button,
806           data->time);
807       g_object_ref_sink (menu);
808       g_object_unref (menu);
809     }
810
811   g_slice_free (MenuPopupData, data);
812
813   return FALSE;
814 }
815
816 static gboolean
817 individual_view_button_press_event_cb (EmpathyIndividualView *view,
818     GdkEventButton *event,
819     gpointer user_data)
820 {
821   if (event->button == 3)
822     {
823       MenuPopupData *data;
824
825       data = g_slice_new (MenuPopupData);
826       data->view = view;
827       data->button = event->button;
828       data->time = event->time;
829       g_idle_add (individual_view_popup_menu_idle_cb, data);
830     }
831
832   return FALSE;
833 }
834
835 static gboolean
836 individual_view_key_press_event_cb (EmpathyIndividualView *view,
837     GdkEventKey *event,
838     gpointer user_data)
839 {
840   if (event->keyval == GDK_Menu)
841     {
842       MenuPopupData *data;
843
844       data = g_slice_new (MenuPopupData);
845       data->view = view;
846       data->button = 0;
847       data->time = event->time;
848       g_idle_add (individual_view_popup_menu_idle_cb, data);
849     }
850
851   return FALSE;
852 }
853
854 static void
855 individual_view_row_activated (GtkTreeView *view,
856     GtkTreePath *path,
857     GtkTreeViewColumn *column)
858 {
859   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
860   FolksIndividual *individual;
861   EmpathyContact *contact = NULL;
862   GtkTreeModel *model;
863   GtkTreeIter iter;
864
865   if (!(priv->individual_features & EMPATHY_CONTACT_FEATURE_CHAT))
866     return;
867
868   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
869   gtk_tree_model_get_iter (model, &iter, path);
870   gtk_tree_model_get (model, &iter,
871       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
872
873   if (individual == NULL)
874     return;
875
876   contact = empathy_contact_dup_from_folks_individual (individual);
877   if (contact != NULL)
878     {
879       DEBUG ("Starting a chat");
880
881       empathy_dispatcher_chat_with_contact (contact,
882           gtk_get_current_event_time (), NULL, NULL);
883     }
884
885   g_object_unref (individual);
886   tp_clear_object (&contact);
887 }
888
889 static void
890 individual_view_call_activated_cb (EmpathyCellRendererActivatable *cell,
891     const gchar *path_string,
892     EmpathyIndividualView *view)
893 {
894   GtkWidget *menu;
895   GtkTreeModel *model;
896   GtkTreeIter iter;
897   FolksIndividual *individual;
898   GdkEventButton *event;
899   GtkMenuShell *shell;
900   GtkWidget *item;
901
902   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
903   if (!gtk_tree_model_get_iter_from_string (model, &iter, path_string))
904     return;
905
906   gtk_tree_model_get (model, &iter,
907       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
908   if (individual == NULL)
909     return;
910
911   event = (GdkEventButton *) gtk_get_current_event ();
912
913   menu = gtk_menu_new ();
914   shell = GTK_MENU_SHELL (menu);
915
916   /* audio */
917   item = empathy_individual_audio_call_menu_item_new (individual);
918   gtk_menu_shell_append (shell, item);
919   gtk_widget_show (item);
920
921   /* video */
922   item = empathy_individual_video_call_menu_item_new (individual);
923   gtk_menu_shell_append (shell, item);
924   gtk_widget_show (item);
925
926   g_signal_connect (menu, "deactivate", G_CALLBACK (gtk_menu_detach), NULL);
927   gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (view), NULL);
928   gtk_widget_show (menu);
929   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
930       event->button, event->time);
931   g_object_ref_sink (menu);
932   g_object_unref (menu);
933
934   g_object_unref (individual);
935 }
936
937 static void
938 individual_view_cell_set_background (EmpathyIndividualView *view,
939     GtkCellRenderer *cell,
940     gboolean is_group,
941     gboolean is_active)
942 {
943   GdkColor color;
944   GtkStyle *style;
945
946   style = gtk_widget_get_style (GTK_WIDGET (view));
947
948   if (!is_group && is_active)
949     {
950       color = style->bg[GTK_STATE_SELECTED];
951
952       /* Here we take the current theme colour and add it to
953        * the colour for white and average the two. This
954        * gives a colour which is inline with the theme but
955        * slightly whiter.
956        */
957       color.red = (color.red + (style->white).red) / 2;
958       color.green = (color.green + (style->white).green) / 2;
959       color.blue = (color.blue + (style->white).blue) / 2;
960
961       g_object_set (cell, "cell-background-gdk", &color, NULL);
962     }
963   else
964     g_object_set (cell, "cell-background-gdk", NULL, NULL);
965 }
966
967 static void
968 individual_view_pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
969     GtkCellRenderer *cell,
970     GtkTreeModel *model,
971     GtkTreeIter *iter,
972     EmpathyIndividualView *view)
973 {
974   GdkPixbuf *pixbuf;
975   gboolean is_group;
976   gboolean is_active;
977
978   gtk_tree_model_get (model, iter,
979       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
980       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
981       EMPATHY_INDIVIDUAL_STORE_COL_ICON_STATUS, &pixbuf, -1);
982
983   g_object_set (cell,
984       "visible", !is_group,
985       "pixbuf", pixbuf,
986       NULL);
987
988   tp_clear_object (&pixbuf);
989
990   individual_view_cell_set_background (view, cell, is_group, is_active);
991 }
992
993 static void
994 individual_view_group_icon_cell_data_func (GtkTreeViewColumn *tree_column,
995     GtkCellRenderer *cell,
996     GtkTreeModel *model,
997     GtkTreeIter *iter,
998     EmpathyIndividualView *view)
999 {
1000   GdkPixbuf *pixbuf = NULL;
1001   gboolean is_group;
1002   gchar *name;
1003
1004   gtk_tree_model_get (model, iter,
1005       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1006       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1007
1008   if (!is_group)
1009     goto out;
1010
1011   if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_FAVORITE))
1012     {
1013       pixbuf = empathy_pixbuf_from_icon_name ("emblem-favorite",
1014           GTK_ICON_SIZE_MENU);
1015     }
1016   else if (!tp_strdiff (name, EMPATHY_INDIVIDUAL_STORE_PEOPLE_NEARBY))
1017     {
1018       pixbuf = empathy_pixbuf_from_icon_name ("im-local-xmpp",
1019           GTK_ICON_SIZE_MENU);
1020     }
1021
1022 out:
1023   g_object_set (cell,
1024       "visible", pixbuf != NULL,
1025       "pixbuf", pixbuf,
1026       NULL);
1027
1028   tp_clear_object (&pixbuf);
1029
1030   g_free (name);
1031 }
1032
1033 static void
1034 individual_view_audio_call_cell_data_func (GtkTreeViewColumn *tree_column,
1035     GtkCellRenderer *cell,
1036     GtkTreeModel *model,
1037     GtkTreeIter *iter,
1038     EmpathyIndividualView *view)
1039 {
1040   gboolean is_group;
1041   gboolean is_active;
1042   gboolean can_audio, can_video;
1043
1044   gtk_tree_model_get (model, iter,
1045       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1046       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active,
1047       EMPATHY_INDIVIDUAL_STORE_COL_CAN_AUDIO_CALL, &can_audio,
1048       EMPATHY_INDIVIDUAL_STORE_COL_CAN_VIDEO_CALL, &can_video, -1);
1049
1050   g_object_set (cell,
1051       "visible", !is_group && (can_audio || can_video),
1052       "icon-name", can_video ? EMPATHY_IMAGE_VIDEO_CALL : EMPATHY_IMAGE_VOIP,
1053       NULL);
1054
1055   individual_view_cell_set_background (view, cell, is_group, is_active);
1056 }
1057
1058 static void
1059 individual_view_avatar_cell_data_func (GtkTreeViewColumn *tree_column,
1060     GtkCellRenderer *cell,
1061     GtkTreeModel *model,
1062     GtkTreeIter *iter,
1063     EmpathyIndividualView *view)
1064 {
1065   GdkPixbuf *pixbuf;
1066   gboolean show_avatar;
1067   gboolean is_group;
1068   gboolean is_active;
1069
1070   gtk_tree_model_get (model, iter,
1071       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR, &pixbuf,
1072       EMPATHY_INDIVIDUAL_STORE_COL_PIXBUF_AVATAR_VISIBLE, &show_avatar,
1073       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1074       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1075
1076   g_object_set (cell,
1077       "visible", !is_group && show_avatar,
1078       "pixbuf", pixbuf,
1079       NULL);
1080
1081   tp_clear_object (&pixbuf);
1082
1083   individual_view_cell_set_background (view, cell, is_group, is_active);
1084 }
1085
1086 static void
1087 individual_view_text_cell_data_func (GtkTreeViewColumn *tree_column,
1088     GtkCellRenderer *cell,
1089     GtkTreeModel *model,
1090     GtkTreeIter *iter,
1091     EmpathyIndividualView *view)
1092 {
1093   gboolean is_group;
1094   gboolean is_active;
1095
1096   gtk_tree_model_get (model, iter,
1097       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1098       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1099
1100   individual_view_cell_set_background (view, cell, is_group, is_active);
1101 }
1102
1103 static void
1104 individual_view_expander_cell_data_func (GtkTreeViewColumn *column,
1105     GtkCellRenderer *cell,
1106     GtkTreeModel *model,
1107     GtkTreeIter *iter,
1108     EmpathyIndividualView *view)
1109 {
1110   gboolean is_group;
1111   gboolean is_active;
1112
1113   gtk_tree_model_get (model, iter,
1114       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1115       EMPATHY_INDIVIDUAL_STORE_COL_IS_ACTIVE, &is_active, -1);
1116
1117   if (gtk_tree_model_iter_has_child (model, iter))
1118     {
1119       GtkTreePath *path;
1120       gboolean row_expanded;
1121
1122       path = gtk_tree_model_get_path (model, iter);
1123       row_expanded =
1124           gtk_tree_view_row_expanded (GTK_TREE_VIEW
1125           (gtk_tree_view_column_get_tree_view (column)), path);
1126       gtk_tree_path_free (path);
1127
1128       g_object_set (cell,
1129           "visible", TRUE,
1130           "expander-style",
1131           row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
1132           NULL);
1133     }
1134   else
1135     g_object_set (cell, "visible", FALSE, NULL);
1136
1137   individual_view_cell_set_background (view, cell, is_group, is_active);
1138 }
1139
1140 static void
1141 individual_view_row_expand_or_collapse_cb (EmpathyIndividualView *view,
1142     GtkTreeIter *iter,
1143     GtkTreePath *path,
1144     gpointer user_data)
1145 {
1146   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1147   GtkTreeModel *model;
1148   gchar *name;
1149   gboolean expanded;
1150
1151   if (!(priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE))
1152     return;
1153
1154   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1155
1156   gtk_tree_model_get (model, iter,
1157       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name, -1);
1158
1159   expanded = GPOINTER_TO_INT (user_data);
1160   empathy_contact_group_set_expanded (name, expanded);
1161
1162   g_free (name);
1163 }
1164
1165 static gboolean
1166 individual_view_start_search_cb (EmpathyIndividualView *view,
1167     gpointer data)
1168 {
1169   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1170
1171   if (priv->search_widget == NULL)
1172     return FALSE;
1173
1174   if (gtk_widget_get_visible (GTK_WIDGET (priv->search_widget)))
1175     gtk_widget_grab_focus (GTK_WIDGET (priv->search_widget));
1176   else
1177     gtk_widget_show (GTK_WIDGET (priv->search_widget));
1178
1179   return TRUE;
1180 }
1181
1182 static void
1183 individual_view_search_text_notify_cb (EmpathyLiveSearch *search,
1184     GParamSpec *pspec,
1185     EmpathyIndividualView *view)
1186 {
1187   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1188   GtkTreePath *path;
1189   GtkTreeViewColumn *focus_column;
1190   GtkTreeModel *model;
1191   GtkTreeIter iter;
1192   gboolean set_cursor = FALSE;
1193
1194   gtk_tree_model_filter_refilter (priv->filter);
1195
1196   /* Set cursor on the first contact. If it is already set on a group,
1197    * set it on its first child contact. Note that first child of a group
1198    * is its separator, that's why we actually set to the 2nd
1199    */
1200
1201   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1202   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1203
1204   if (path == NULL)
1205     {
1206       path = gtk_tree_path_new_from_string ("0:1");
1207       set_cursor = TRUE;
1208     }
1209   else if (gtk_tree_path_get_depth (path) < 2)
1210     {
1211       gboolean is_group;
1212
1213       gtk_tree_model_get_iter (model, &iter, path);
1214       gtk_tree_model_get (model, &iter,
1215           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1216           -1);
1217
1218       if (is_group)
1219         {
1220           gtk_tree_path_down (path);
1221           gtk_tree_path_next (path);
1222           set_cursor = TRUE;
1223         }
1224     }
1225
1226   if (set_cursor)
1227     {
1228       /* FIXME: Workaround for GTK bug #621651, we have to make sure the path is
1229        * valid. */
1230       if (gtk_tree_model_get_iter (model, &iter, path))
1231         {
1232           gtk_tree_view_set_cursor (GTK_TREE_VIEW (view), path, focus_column,
1233               FALSE);
1234         }
1235     }
1236
1237   gtk_tree_path_free (path);
1238 }
1239
1240 static void
1241 individual_view_search_activate_cb (GtkWidget *search,
1242   EmpathyIndividualView *view)
1243 {
1244   GtkTreePath *path;
1245   GtkTreeViewColumn *focus_column;
1246
1247   gtk_tree_view_get_cursor (GTK_TREE_VIEW (view), &path, &focus_column);
1248   if (path != NULL)
1249     {
1250       gtk_tree_view_row_activated (GTK_TREE_VIEW (view), path, focus_column);
1251       gtk_tree_path_free (path);
1252
1253       gtk_widget_hide (search);
1254     }
1255 }
1256
1257 static void
1258 individual_view_search_hide_cb (EmpathyLiveSearch *search,
1259     EmpathyIndividualView *view)
1260 {
1261   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1262   GtkTreeModel *model;
1263   GtkTreeIter iter;
1264   gboolean valid = FALSE;
1265
1266   /* block expand or collapse handlers, they would write the
1267    * expand or collapsed setting to file otherwise */
1268   g_signal_handlers_block_by_func (view,
1269       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1270   g_signal_handlers_block_by_func (view,
1271     individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1272
1273   /* restore which groups are expanded and which are not */
1274   model = gtk_tree_view_get_model (GTK_TREE_VIEW (view));
1275   for (valid = gtk_tree_model_get_iter_first (model, &iter);
1276        valid; valid = gtk_tree_model_iter_next (model, &iter))
1277     {
1278       gboolean is_group;
1279       gchar *name = NULL;
1280       GtkTreePath *path;
1281
1282       gtk_tree_model_get (model, &iter,
1283           EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1284           EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1285           -1);
1286
1287       if (!is_group)
1288         {
1289           g_free (name);
1290           continue;
1291         }
1292
1293       path = gtk_tree_model_get_path (model, &iter);
1294       if ((priv->view_features &
1295             EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1296           empathy_contact_group_get_expanded (name))
1297         {
1298           gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, TRUE);
1299         }
1300       else
1301         {
1302           gtk_tree_view_collapse_row (GTK_TREE_VIEW (view), path);
1303         }
1304
1305       gtk_tree_path_free (path);
1306       g_free (name);
1307     }
1308
1309   /* unblock expand or collapse handlers */
1310   g_signal_handlers_unblock_by_func (view,
1311       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1312   g_signal_handlers_unblock_by_func (view,
1313       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (FALSE));
1314 }
1315
1316 static void
1317 individual_view_search_show_cb (EmpathyLiveSearch *search,
1318     EmpathyIndividualView *view)
1319 {
1320   /* block expand or collapse handlers during expand all, they would
1321    * write the expand or collapsed setting to file otherwise */
1322   g_signal_handlers_block_by_func (view,
1323       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1324
1325   gtk_tree_view_expand_all (GTK_TREE_VIEW (view));
1326
1327   g_signal_handlers_unblock_by_func (view,
1328       individual_view_row_expand_or_collapse_cb, GINT_TO_POINTER (TRUE));
1329 }
1330
1331 typedef struct {
1332   EmpathyIndividualView *view;
1333   GtkTreeRowReference *row_ref;
1334   gboolean expand;
1335 } ExpandData;
1336
1337 static gboolean
1338 individual_view_expand_idle_cb (gpointer user_data)
1339 {
1340   ExpandData *data = user_data;
1341   GtkTreePath *path;
1342
1343   path = gtk_tree_row_reference_get_path (data->row_ref);
1344   if (path == NULL)
1345     goto done;
1346
1347   g_signal_handlers_block_by_func (data->view,
1348     individual_view_row_expand_or_collapse_cb,
1349     GINT_TO_POINTER (data->expand));
1350
1351   if (data->expand)
1352     gtk_tree_view_expand_row (GTK_TREE_VIEW (data->view), path, TRUE);
1353   else
1354     gtk_tree_view_collapse_row (GTK_TREE_VIEW (data->view), path);
1355
1356   gtk_tree_path_free (path);
1357
1358   g_signal_handlers_unblock_by_func (data->view,
1359       individual_view_row_expand_or_collapse_cb,
1360       GINT_TO_POINTER (data->expand));
1361
1362 done:
1363   g_object_unref (data->view);
1364   gtk_tree_row_reference_free (data->row_ref);
1365   g_slice_free (ExpandData, data);
1366
1367   return FALSE;
1368 }
1369
1370 static void
1371 individual_view_row_has_child_toggled_cb (GtkTreeModel *model,
1372     GtkTreePath *path,
1373     GtkTreeIter *iter,
1374     EmpathyIndividualView *view)
1375 {
1376   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1377   gboolean is_group = FALSE;
1378   gchar *name = NULL;
1379   ExpandData *data;
1380
1381   gtk_tree_model_get (model, iter,
1382       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1383       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1384       -1);
1385
1386   if (!is_group || EMP_STR_EMPTY (name))
1387     {
1388       g_free (name);
1389       return;
1390     }
1391
1392   data = g_slice_new0 (ExpandData);
1393   data->view = g_object_ref (view);
1394   data->row_ref = gtk_tree_row_reference_new (model, path);
1395   data->expand =
1396       (priv->view_features &
1397           EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_SAVE) == 0 ||
1398       (priv->search_widget != NULL &&
1399           gtk_widget_get_visible (priv->search_widget)) ||
1400       empathy_contact_group_get_expanded (name);
1401
1402   /* FIXME: It doesn't work to call gtk_tree_view_expand_row () from within
1403    * gtk_tree_model_filter_refilter () */
1404   g_idle_add (individual_view_expand_idle_cb, data);
1405
1406   g_free (name);
1407 }
1408
1409 static void
1410 individual_view_verify_group_visibility (EmpathyIndividualView *view,
1411     GtkTreePath *path)
1412 {
1413   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1414   GtkTreeModel *model;
1415   GtkTreePath *parent_path;
1416   GtkTreeIter parent_iter;
1417
1418   if (gtk_tree_path_get_depth (path) < 2)
1419     return;
1420
1421   /* A group row is visible if and only if at least one if its child is visible.
1422    * So when a row is inserted/deleted/changed in the base model, that could
1423    * modify the visibility of its parent in the filter model.
1424   */
1425
1426   model = GTK_TREE_MODEL (priv->store);
1427   parent_path = gtk_tree_path_copy (path);
1428   gtk_tree_path_up (parent_path);
1429   if (gtk_tree_model_get_iter (model, &parent_iter, parent_path))
1430     {
1431       /* This tells the filter to verify the visibility of that row, and
1432        * show/hide it if necessary */
1433       gtk_tree_model_row_changed (GTK_TREE_MODEL (priv->store),
1434               parent_path, &parent_iter);
1435     }
1436   gtk_tree_path_free (parent_path);
1437 }
1438
1439 static void
1440 individual_view_store_row_changed_cb (GtkTreeModel *model,
1441   GtkTreePath *path,
1442   GtkTreeIter *iter,
1443   EmpathyIndividualView *view)
1444 {
1445   individual_view_verify_group_visibility (view, path);
1446 }
1447
1448 static void
1449 individual_view_store_row_deleted_cb (GtkTreeModel *model,
1450   GtkTreePath *path,
1451   EmpathyIndividualView *view)
1452 {
1453   individual_view_verify_group_visibility (view, path);
1454 }
1455
1456 static void
1457 individual_view_constructed (GObject *object)
1458 {
1459   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1460   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1461   GtkCellRenderer *cell;
1462   GtkTreeViewColumn *col;
1463   guint i;
1464
1465   priv->filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (
1466       GTK_TREE_MODEL (priv->store), NULL));
1467   gtk_tree_model_filter_set_visible_func (priv->filter,
1468       individual_view_filter_visible_func, view, NULL);
1469
1470   g_signal_connect (priv->store, "row-has-child-toggled",
1471       G_CALLBACK (individual_view_row_has_child_toggled_cb), view);
1472   gtk_tree_view_set_model (GTK_TREE_VIEW (view),
1473       GTK_TREE_MODEL (priv->filter));
1474
1475   tp_g_signal_connect_object (priv->store, "row-changed",
1476       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1477   tp_g_signal_connect_object (priv->store, "row-inserted",
1478       G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1479   tp_g_signal_connect_object (priv->store, "row-deleted",
1480       G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1481
1482   tp_g_signal_connect_object (priv->store, "row-changed",
1483     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1484   tp_g_signal_connect_object (priv->store, "row-inserted",
1485     G_CALLBACK (individual_view_store_row_changed_cb), view, 0);
1486   tp_g_signal_connect_object (priv->store, "row-deleted",
1487     G_CALLBACK (individual_view_store_row_deleted_cb), view, 0);
1488
1489   /* Setup view */
1490   /* Setting reorderable is a hack that gets us row previews as drag icons
1491      for free.  We override all the drag handlers.  It's tricky to get the
1492      position of the drag icon right in drag_begin.  GtkTreeView has special
1493      voodoo for it, so we let it do the voodoo that he do.
1494    */
1495   g_object_set (view,
1496       "headers-visible", FALSE,
1497       "reorderable", TRUE,
1498       "show-expanders", FALSE,
1499       NULL);
1500
1501   col = gtk_tree_view_column_new ();
1502
1503   /* State */
1504   cell = gtk_cell_renderer_pixbuf_new ();
1505   gtk_tree_view_column_pack_start (col, cell, FALSE);
1506   gtk_tree_view_column_set_cell_data_func (col, cell,
1507       (GtkTreeCellDataFunc) individual_view_pixbuf_cell_data_func,
1508       view, NULL);
1509
1510   g_object_set (cell,
1511       "xpad", 5,
1512       "ypad", 1,
1513       "visible", FALSE,
1514       NULL);
1515
1516   /* Group icon */
1517   cell = gtk_cell_renderer_pixbuf_new ();
1518   gtk_tree_view_column_pack_start (col, cell, FALSE);
1519   gtk_tree_view_column_set_cell_data_func (col, cell,
1520       (GtkTreeCellDataFunc) individual_view_group_icon_cell_data_func,
1521       view, NULL);
1522
1523   g_object_set (cell,
1524       "xpad", 0,
1525       "ypad", 0,
1526       "visible", FALSE,
1527       "width", 16,
1528       "height", 16,
1529       NULL);
1530
1531   /* Name */
1532   cell = empathy_cell_renderer_text_new ();
1533   gtk_tree_view_column_pack_start (col, cell, TRUE);
1534   gtk_tree_view_column_set_cell_data_func (col, cell,
1535       (GtkTreeCellDataFunc) individual_view_text_cell_data_func, view, NULL);
1536
1537   gtk_tree_view_column_add_attribute (col, cell,
1538       "name", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1539   gtk_tree_view_column_add_attribute (col, cell,
1540       "text", EMPATHY_INDIVIDUAL_STORE_COL_NAME);
1541   gtk_tree_view_column_add_attribute (col, cell,
1542       "presence-type", EMPATHY_INDIVIDUAL_STORE_COL_PRESENCE_TYPE);
1543   gtk_tree_view_column_add_attribute (col, cell,
1544       "status", EMPATHY_INDIVIDUAL_STORE_COL_STATUS);
1545   gtk_tree_view_column_add_attribute (col, cell,
1546       "is_group", EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP);
1547   gtk_tree_view_column_add_attribute (col, cell,
1548       "compact", EMPATHY_INDIVIDUAL_STORE_COL_COMPACT);
1549
1550   /* Audio Call Icon */
1551   cell = empathy_cell_renderer_activatable_new ();
1552   gtk_tree_view_column_pack_start (col, cell, FALSE);
1553   gtk_tree_view_column_set_cell_data_func (col, cell,
1554       (GtkTreeCellDataFunc) individual_view_audio_call_cell_data_func,
1555       view, NULL);
1556
1557   g_object_set (cell, "visible", FALSE, NULL);
1558
1559   g_signal_connect (cell, "path-activated",
1560       G_CALLBACK (individual_view_call_activated_cb), view);
1561
1562   /* Avatar */
1563   cell = gtk_cell_renderer_pixbuf_new ();
1564   gtk_tree_view_column_pack_start (col, cell, FALSE);
1565   gtk_tree_view_column_set_cell_data_func (col, cell,
1566       (GtkTreeCellDataFunc) individual_view_avatar_cell_data_func,
1567       view, NULL);
1568
1569   g_object_set (cell,
1570       "xpad", 0,
1571       "ypad", 0,
1572       "visible", FALSE,
1573       "width", 32,
1574       "height", 32,
1575       NULL);
1576
1577   /* Expander */
1578   cell = empathy_cell_renderer_expander_new ();
1579   gtk_tree_view_column_pack_end (col, cell, FALSE);
1580   gtk_tree_view_column_set_cell_data_func (col, cell,
1581       (GtkTreeCellDataFunc) individual_view_expander_cell_data_func,
1582       view, NULL);
1583
1584   /* Actually add the column now we have added all cell renderers */
1585   gtk_tree_view_append_column (GTK_TREE_VIEW (view), col);
1586
1587   /* Drag & Drop. */
1588   for (i = 0; i < G_N_ELEMENTS (drag_types_dest); ++i)
1589     {
1590       drag_atoms_dest[i] = gdk_atom_intern (drag_types_dest[i].target, FALSE);
1591     }
1592
1593   for (i = 0; i < G_N_ELEMENTS (drag_types_source); ++i)
1594     {
1595       drag_atoms_source[i] = gdk_atom_intern (drag_types_source[i].target,
1596           FALSE);
1597     }
1598 }
1599
1600 static void
1601 individual_view_set_view_features (EmpathyIndividualView *view,
1602     EmpathyIndividualFeatureFlags features)
1603 {
1604   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1605   gboolean has_tooltip;
1606
1607   g_return_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view));
1608
1609   priv->view_features = features;
1610
1611   /* Update DnD source/dest */
1612   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DRAG)
1613     {
1614       gtk_drag_source_set (GTK_WIDGET (view),
1615           GDK_BUTTON1_MASK,
1616           drag_types_source,
1617           G_N_ELEMENTS (drag_types_source),
1618           GDK_ACTION_MOVE | GDK_ACTION_COPY);
1619     }
1620   else
1621     {
1622       gtk_drag_source_unset (GTK_WIDGET (view));
1623
1624     }
1625
1626   if (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_DROP)
1627     {
1628       gtk_drag_dest_set (GTK_WIDGET (view),
1629           GTK_DEST_DEFAULT_ALL,
1630           drag_types_dest,
1631           G_N_ELEMENTS (drag_types_dest), GDK_ACTION_MOVE | GDK_ACTION_COPY);
1632     }
1633   else
1634     {
1635       /* FIXME: URI could still be droped depending on FT feature */
1636       gtk_drag_dest_unset (GTK_WIDGET (view));
1637     }
1638
1639   /* Update has-tooltip */
1640   has_tooltip =
1641       (features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_TOOLTIP) != 0;
1642   gtk_widget_set_has_tooltip (GTK_WIDGET (view), has_tooltip);
1643 }
1644
1645 static void
1646 individual_view_dispose (GObject *object)
1647 {
1648   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1649   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1650
1651   tp_clear_object (&priv->store);
1652   tp_clear_object (&priv->filter);
1653   tp_clear_pointer (&priv->tooltip_widget, gtk_widget_destroy);
1654   tp_clear_pointer (&priv->file_targets, gtk_target_list_unref);
1655
1656   empathy_individual_view_set_live_search (view, NULL);
1657
1658   G_OBJECT_CLASS (empathy_individual_view_parent_class)->dispose (object);
1659 }
1660
1661 static void
1662 individual_view_get_property (GObject *object,
1663     guint param_id,
1664     GValue *value,
1665     GParamSpec *pspec)
1666 {
1667   EmpathyIndividualViewPriv *priv;
1668
1669   priv = GET_PRIV (object);
1670
1671   switch (param_id)
1672     {
1673     case PROP_STORE:
1674       g_value_set_object (value, priv->store);
1675       break;
1676     case PROP_VIEW_FEATURES:
1677       g_value_set_flags (value, priv->view_features);
1678       break;
1679     case PROP_INDIVIDUAL_FEATURES:
1680       g_value_set_flags (value, priv->individual_features);
1681       break;
1682     default:
1683       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1684       break;
1685     };
1686 }
1687
1688 static void
1689 individual_view_set_property (GObject *object,
1690     guint param_id,
1691     const GValue *value,
1692     GParamSpec *pspec)
1693 {
1694   EmpathyIndividualView *view = EMPATHY_INDIVIDUAL_VIEW (object);
1695   EmpathyIndividualViewPriv *priv = GET_PRIV (object);
1696
1697   switch (param_id)
1698     {
1699     case PROP_STORE:
1700       priv->store = g_value_dup_object (value);
1701       break;
1702     case PROP_VIEW_FEATURES:
1703       individual_view_set_view_features (view, g_value_get_flags (value));
1704       break;
1705     case PROP_INDIVIDUAL_FEATURES:
1706       priv->individual_features = g_value_get_flags (value);
1707       break;
1708     default:
1709       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1710       break;
1711     };
1712 }
1713
1714 static void
1715 empathy_individual_view_class_init (EmpathyIndividualViewClass *klass)
1716 {
1717   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1718   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1719   GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
1720
1721   object_class->constructed = individual_view_constructed;
1722   object_class->dispose = individual_view_dispose;
1723   object_class->get_property = individual_view_get_property;
1724   object_class->set_property = individual_view_set_property;
1725
1726   widget_class->drag_data_received = individual_view_drag_data_received;
1727   widget_class->drag_drop = individual_view_drag_drop;
1728   widget_class->drag_begin = individual_view_drag_begin;
1729   widget_class->drag_data_get = individual_view_drag_data_get;
1730   widget_class->drag_end = individual_view_drag_end;
1731   widget_class->drag_motion = individual_view_drag_motion;
1732
1733   /* We use the class method to let user of this widget to connect to
1734    * the signal and stop emission of the signal so the default handler
1735    * won't be called. */
1736   tree_view_class->row_activated = individual_view_row_activated;
1737
1738   signals[DRAG_CONTACT_RECEIVED] =
1739       g_signal_new ("drag-contact-received",
1740       G_OBJECT_CLASS_TYPE (klass),
1741       G_SIGNAL_RUN_LAST,
1742       0,
1743       NULL, NULL,
1744       _empathy_gtk_marshal_VOID__OBJECT_STRING_STRING,
1745       G_TYPE_NONE, 3, EMPATHY_TYPE_CONTACT, G_TYPE_STRING, G_TYPE_STRING);
1746
1747   g_object_class_install_property (object_class,
1748       PROP_STORE,
1749       g_param_spec_object ("store",
1750           "The store of the view",
1751           "The store of the view",
1752           EMPATHY_TYPE_INDIVIDUAL_STORE,
1753           G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
1754   g_object_class_install_property (object_class,
1755       PROP_VIEW_FEATURES,
1756       g_param_spec_flags ("view-features",
1757           "Features of the view",
1758           "Flags for all enabled features",
1759           EMPATHY_TYPE_INDIVIDUAL_VIEW_FEATURE_FLAGS,
1760           EMPATHY_INDIVIDUAL_VIEW_FEATURE_NONE, G_PARAM_READWRITE));
1761   g_object_class_install_property (object_class,
1762       PROP_INDIVIDUAL_FEATURES,
1763       g_param_spec_flags ("individual-features",
1764           "Features of the contact menu",
1765           "Flags for all enabled features for the menu",
1766           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1767           EMPATHY_CONTACT_FEATURE_NONE, G_PARAM_READWRITE));
1768
1769   g_type_class_add_private (object_class, sizeof (EmpathyIndividualViewPriv));
1770 }
1771
1772 static void
1773 empathy_individual_view_init (EmpathyIndividualView *view)
1774 {
1775   EmpathyIndividualViewPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (view,
1776       EMPATHY_TYPE_INDIVIDUAL_VIEW, EmpathyIndividualViewPriv);
1777
1778   view->priv = priv;
1779   /* Get saved group states. */
1780   empathy_contact_groups_get_all ();
1781
1782   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (view),
1783       empathy_individual_store_row_separator_func, NULL, NULL);
1784
1785   /* Set up drag target lists. */
1786   priv->file_targets = gtk_target_list_new (drag_types_dest_file,
1787       G_N_ELEMENTS (drag_types_dest_file));
1788
1789   /* Connect to tree view signals rather than override. */
1790   g_signal_connect (view, "button-press-event",
1791       G_CALLBACK (individual_view_button_press_event_cb), NULL);
1792   g_signal_connect (view, "key-press-event",
1793       G_CALLBACK (individual_view_key_press_event_cb), NULL);
1794   g_signal_connect (view, "row-expanded",
1795       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1796       GINT_TO_POINTER (TRUE));
1797   g_signal_connect (view, "row-collapsed",
1798       G_CALLBACK (individual_view_row_expand_or_collapse_cb),
1799       GINT_TO_POINTER (FALSE));
1800   g_signal_connect (view, "query-tooltip",
1801       G_CALLBACK (individual_view_query_tooltip_cb), NULL);
1802 }
1803
1804 EmpathyIndividualView *
1805 empathy_individual_view_new (EmpathyIndividualStore *store,
1806     EmpathyIndividualViewFeatureFlags view_features,
1807     EmpathyIndividualFeatureFlags individual_features)
1808 {
1809   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1810
1811   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_VIEW,
1812       "store", store,
1813       "individual-features", individual_features,
1814       "view-features", view_features, NULL);
1815 }
1816
1817 FolksIndividual *
1818 empathy_individual_view_dup_selected (EmpathyIndividualView *view)
1819 {
1820   EmpathyIndividualViewPriv *priv;
1821   GtkTreeSelection *selection;
1822   GtkTreeIter iter;
1823   GtkTreeModel *model;
1824   FolksIndividual *individual;
1825
1826   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1827
1828   priv = GET_PRIV (view);
1829
1830   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1831   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1832     return NULL;
1833
1834   gtk_tree_model_get (model, &iter,
1835       EMPATHY_INDIVIDUAL_STORE_COL_INDIVIDUAL, &individual, -1);
1836
1837   return individual;
1838 }
1839
1840 EmpathyIndividualManagerFlags
1841 empathy_individual_view_get_flags (EmpathyIndividualView *view)
1842 {
1843   EmpathyIndividualViewPriv *priv;
1844   GtkTreeSelection *selection;
1845   GtkTreeIter iter;
1846   GtkTreeModel *model;
1847   EmpathyIndividualFeatureFlags flags;
1848
1849   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), 0);
1850
1851   priv = GET_PRIV (view);
1852
1853   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1854   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1855     return 0;
1856
1857   gtk_tree_model_get (model, &iter,
1858       EMPATHY_INDIVIDUAL_STORE_COL_FLAGS, &flags, -1);
1859
1860   return flags;
1861 }
1862
1863 gchar *
1864 empathy_individual_view_get_selected_group (EmpathyIndividualView *view,
1865     gboolean *is_fake_group)
1866 {
1867   EmpathyIndividualViewPriv *priv;
1868   GtkTreeSelection *selection;
1869   GtkTreeIter iter;
1870   GtkTreeModel *model;
1871   gboolean is_group;
1872   gchar *name;
1873   gboolean fake;
1874
1875   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1876
1877   priv = GET_PRIV (view);
1878
1879   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
1880   if (!gtk_tree_selection_get_selected (selection, &model, &iter))
1881     return NULL;
1882
1883   gtk_tree_model_get (model, &iter,
1884       EMPATHY_INDIVIDUAL_STORE_COL_IS_GROUP, &is_group,
1885       EMPATHY_INDIVIDUAL_STORE_COL_NAME, &name,
1886       EMPATHY_INDIVIDUAL_STORE_COL_IS_FAKE_GROUP, &fake, -1);
1887
1888   if (!is_group)
1889     {
1890       g_free (name);
1891       return NULL;
1892     }
1893
1894   if (is_fake_group != NULL)
1895     *is_fake_group = fake;
1896
1897   return name;
1898 }
1899
1900 static gboolean
1901 individual_view_remove_dialog_show (GtkWindow *parent,
1902     const gchar *message,
1903     const gchar *secondary_text)
1904 {
1905   GtkWidget *dialog;
1906   gboolean res;
1907
1908   dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
1909       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
1910   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
1911       GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1912       GTK_STOCK_DELETE, GTK_RESPONSE_YES, NULL);
1913   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
1914       "%s", secondary_text);
1915
1916   gtk_widget_show (dialog);
1917
1918   res = gtk_dialog_run (GTK_DIALOG (dialog));
1919   gtk_widget_destroy (dialog);
1920
1921   return (res == GTK_RESPONSE_YES);
1922 }
1923
1924 static void
1925 individual_view_group_remove_activate_cb (GtkMenuItem *menuitem,
1926     EmpathyIndividualView *view)
1927 {
1928   gchar *group;
1929
1930   group = empathy_individual_view_get_selected_group (view, NULL);
1931   if (group != NULL)
1932     {
1933       gchar *text;
1934       GtkWindow *parent;
1935
1936       text =
1937           g_strdup_printf (_("Do you really want to remove the group '%s'?"),
1938           group);
1939       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
1940       if (individual_view_remove_dialog_show (parent, _("Removing group"),
1941               text))
1942         {
1943           EmpathyIndividualManager *manager =
1944               empathy_individual_manager_dup_singleton ();
1945           empathy_individual_manager_remove_group (manager, group);
1946           g_object_unref (G_OBJECT (manager));
1947         }
1948
1949       g_free (text);
1950     }
1951
1952   g_free (group);
1953 }
1954
1955 GtkWidget *
1956 empathy_individual_view_get_group_menu (EmpathyIndividualView *view)
1957 {
1958   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
1959   gchar *group;
1960   GtkWidget *menu;
1961   GtkWidget *item;
1962   GtkWidget *image;
1963   gboolean is_fake_group;
1964
1965   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
1966
1967   if (!(priv->view_features & (EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME |
1968               EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)))
1969     return NULL;
1970
1971   group = empathy_individual_view_get_selected_group (view, &is_fake_group);
1972   if (!group || is_fake_group)
1973     {
1974       /* We can't alter fake groups */
1975       return NULL;
1976     }
1977
1978   menu = gtk_menu_new ();
1979
1980   /* TODO: implement
1981      if (priv->view_features &
1982      EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_RENAME) {
1983      item = gtk_menu_item_new_with_mnemonic (_("Re_name"));
1984      gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1985      gtk_widget_show (item);
1986      g_signal_connect (item, "activate",
1987      G_CALLBACK (individual_view_group_rename_activate_cb),
1988      view);
1989      }
1990    */
1991
1992   if (priv->view_features & EMPATHY_INDIVIDUAL_VIEW_FEATURE_GROUPS_REMOVE)
1993     {
1994       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
1995       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
1996           GTK_ICON_SIZE_MENU);
1997       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1998       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1999       gtk_widget_show (item);
2000       g_signal_connect (item, "activate",
2001           G_CALLBACK (individual_view_group_remove_activate_cb), view);
2002     }
2003
2004   g_free (group);
2005
2006   return menu;
2007 }
2008
2009 static void
2010 individual_view_remove_activate_cb (GtkMenuItem *menuitem,
2011     EmpathyIndividualView *view)
2012 {
2013   FolksIndividual *individual;
2014
2015   individual = empathy_individual_view_dup_selected (view);
2016
2017   if (individual != NULL)
2018     {
2019       gchar *text;
2020       GtkWindow *parent;
2021
2022       parent = empathy_get_toplevel_window (GTK_WIDGET (view));
2023       text =
2024           g_strdup_printf (_
2025           ("Do you really want to remove the contact '%s'?"),
2026           folks_individual_get_alias (individual));
2027       if (individual_view_remove_dialog_show (parent, _("Removing contact"),
2028               text))
2029         {
2030           EmpathyIndividualManager *manager;
2031
2032           manager = empathy_individual_manager_dup_singleton ();
2033           empathy_individual_manager_remove (manager, individual, "");
2034           g_object_unref (G_OBJECT (manager));
2035         }
2036
2037       g_free (text);
2038       g_object_unref (individual);
2039     }
2040 }
2041
2042 GtkWidget *
2043 empathy_individual_view_get_individual_menu (EmpathyIndividualView *view)
2044 {
2045   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2046   FolksIndividual *individual;
2047   GtkWidget *menu = NULL;
2048   GtkWidget *item;
2049   GtkWidget *image;
2050   EmpathyIndividualManagerFlags flags;
2051
2052   g_return_val_if_fail (EMPATHY_IS_INDIVIDUAL_VIEW (view), NULL);
2053
2054   individual = empathy_individual_view_dup_selected (view);
2055   if (individual == NULL)
2056     return NULL;
2057
2058   flags = empathy_individual_view_get_flags (view);
2059
2060   menu = empathy_individual_menu_new (individual, priv->individual_features);
2061
2062   /* Remove contact */
2063   if (priv->view_features &
2064       EMPATHY_INDIVIDUAL_VIEW_FEATURE_CONTACT_REMOVE &&
2065       flags & EMPATHY_INDIVIDUAL_MANAGER_CAN_REMOVE)
2066     {
2067
2068       /* create the menu if required, or just add a separator */
2069       if (menu == NULL)
2070         menu = gtk_menu_new ();
2071       else
2072         {
2073           item = gtk_separator_menu_item_new ();
2074           gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2075           gtk_widget_show (item);
2076         }
2077
2078       /* Remove */
2079       item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
2080       image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
2081           GTK_ICON_SIZE_MENU);
2082       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2083       gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
2084       gtk_widget_show (item);
2085       g_signal_connect (item, "activate",
2086           G_CALLBACK (individual_view_remove_activate_cb), view);
2087     }
2088
2089   g_object_unref (individual);
2090
2091   return menu;
2092 }
2093
2094 void
2095 empathy_individual_view_set_live_search (EmpathyIndividualView *view,
2096     EmpathyLiveSearch *search)
2097 {
2098   EmpathyIndividualViewPriv *priv = GET_PRIV (view);
2099
2100   /* remove old handlers if old search was not null */
2101   if (priv->search_widget != NULL)
2102     {
2103       g_signal_handlers_disconnect_by_func (view,
2104           individual_view_start_search_cb, NULL);
2105
2106       g_signal_handlers_disconnect_by_func (priv->search_widget,
2107           individual_view_search_text_notify_cb, view);
2108       g_signal_handlers_disconnect_by_func (priv->search_widget,
2109           individual_view_search_activate_cb, view);
2110       g_signal_handlers_disconnect_by_func (priv->search_widget,
2111           individual_view_search_hide_cb, view);
2112       g_signal_handlers_disconnect_by_func (priv->search_widget,
2113           individual_view_search_show_cb, view);
2114       g_object_unref (priv->search_widget);
2115       priv->search_widget = NULL;
2116     }
2117
2118   /* connect handlers if new search is not null */
2119   if (search != NULL)
2120     {
2121       priv->search_widget = g_object_ref (search);
2122
2123       g_signal_connect (view, "start-interactive-search",
2124           G_CALLBACK (individual_view_start_search_cb), NULL);
2125
2126       g_signal_connect (priv->search_widget, "notify::text",
2127           G_CALLBACK (individual_view_search_text_notify_cb), view);
2128       g_signal_connect (priv->search_widget, "activate",
2129           G_CALLBACK (individual_view_search_activate_cb), view);
2130       g_signal_connect (priv->search_widget, "hide",
2131           G_CALLBACK (individual_view_search_hide_cb), view);
2132       g_signal_connect (priv->search_widget, "show",
2133           G_CALLBACK (individual_view_search_show_cb), view);
2134     }
2135 }