]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
individual-menu: use the proper persona in submenus
[empathy.git] / libempathy-gtk / empathy-individual-menu.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2008-2010 Collabora Ltd.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Xavier Claessens <xclaesse@gmail.com>
20  *          Travis Reitter <travis.reitter@collabora.co.uk>
21  */
22
23 #include "config.h"
24 #include "empathy-individual-menu.h"
25
26 #include <glib/gi18n-lib.h>
27 #include <tp-account-widgets/tpaw-camera-monitor.h>
28 #include <telepathy-glib/telepathy-glib-dbus.h>
29
30 #include "empathy-account-selector-dialog.h"
31 #include "empathy-call-utils.h"
32 #include "empathy-chatroom-manager.h"
33 #include "empathy-gtk-enum-types.h"
34 #include "empathy-images.h"
35 #include "empathy-individual-dialogs.h"
36 #include "empathy-individual-dialogs.h"
37 #include "empathy-individual-edit-dialog.h"
38 #include "empathy-individual-information-dialog.h"
39 #include "empathy-individual-manager.h"
40 #include "empathy-individual-store-channel.h"
41 #include "empathy-log-window.h"
42 #include "empathy-request-util.h"
43 #include "empathy-share-my-desktop.h"
44 #include "empathy-ui-utils.h"
45 #include "empathy-utils.h"
46
47 #define DEBUG_FLAG EMPATHY_DEBUG_CONTACT
48 #include "empathy-debug.h"
49
50 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyIndividualMenu)
51
52 typedef struct {
53   gchar *active_group; /* may be NULL */
54   FolksIndividual *individual; /* owned */
55   EmpathyIndividualFeatureFlags features;
56   EmpathyIndividualStore *store; /* may be NULL */
57 } EmpathyIndividualMenuPriv;
58
59 enum {
60   PROP_ACTIVE_GROUP = 1,
61   PROP_INDIVIDUAL,
62   PROP_FEATURES,
63   PROP_STORE,
64 };
65
66 enum {
67   MENU_ITEM_ACTIVATED,
68   LAST_SIGNAL
69 };
70
71 static guint signals [LAST_SIGNAL] = { 0 };
72
73 G_DEFINE_TYPE (EmpathyIndividualMenu, empathy_individual_menu, GTK_TYPE_MENU);
74
75 static GtkWidget * chat_menu_item_new_individual (EmpathyIndividualMenu *self,
76     FolksIndividual *individual);
77 static GtkWidget * chat_menu_item_new_contact (EmpathyIndividualMenu *self,
78     EmpathyContact *contact);
79 static GtkWidget * sms_menu_item_new_individual (EmpathyIndividualMenu *self,
80     FolksIndividual *individual);
81 static GtkWidget * sms_menu_item_new_contact (EmpathyIndividualMenu *self,
82     EmpathyContact *contact);
83 static GtkWidget * audio_call_menu_item_new_contact (
84     EmpathyIndividualMenu *self,
85     EmpathyContact *contact);
86 static GtkWidget * video_call_menu_item_new_contact (
87     EmpathyIndividualMenu *self,
88     EmpathyContact *contact);
89 static GtkWidget * log_menu_item_new_individual  (FolksIndividual *individual);
90 static GtkWidget * log_menu_item_new_contact (EmpathyContact *contact);
91 static GtkWidget * info_menu_item_new_individual (FolksIndividual *individual);
92 static GtkWidget * edit_menu_item_new_individual (FolksIndividual *individual);
93 static GtkWidget * invite_menu_item_new (FolksIndividual *individual,
94     EmpathyContact *contact);
95 static GtkWidget * file_transfer_menu_item_new_individual (EmpathyIndividualMenu *self,
96     FolksIndividual *individual);
97 static GtkWidget * file_transfer_menu_item_new_contact (
98     EmpathyIndividualMenu *self,
99     EmpathyContact *contact);
100 static GtkWidget * share_my_desktop_menu_item_new_individual (EmpathyIndividualMenu *self,
101     FolksIndividual *individual);
102 static GtkWidget * share_my_desktop_menu_item_new_contact (
103     EmpathyIndividualMenu *self,
104     EmpathyContact *contact);
105 static GtkWidget * favourite_menu_item_new_individual (FolksIndividual *individual);
106 static GtkWidget * add_menu_item_new_individual (EmpathyIndividualMenu *self,
107     FolksIndividual *individual);
108 static GtkWidget * block_menu_item_new_individual (FolksIndividual *individual);
109 static GtkWidget * remove_menu_item_new_individual (EmpathyIndividualMenu *self);
110
111 static void
112 individual_menu_add_personas (EmpathyIndividualMenu *self,
113     GtkMenuShell *menu,
114     FolksIndividual *individual,
115     EmpathyIndividualFeatureFlags features)
116 {
117   GtkWidget *item;
118   GeeSet *personas;
119   GeeIterator *iter;
120   guint persona_count = 0;
121
122   g_return_if_fail (GTK_IS_MENU (menu));
123   g_return_if_fail (FOLKS_IS_INDIVIDUAL (individual));
124   g_return_if_fail (empathy_folks_individual_contains_contact (individual));
125
126   personas = folks_individual_get_personas (individual);
127   /* we'll re-use this iterator throughout */
128   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
129
130   /* Make sure we've got enough valid entries for these menu items to add
131    * functionality */
132   while (gee_iterator_next (iter))
133     {
134       FolksPersona *persona = gee_iterator_get (iter);
135       if (empathy_folks_persona_is_interesting (persona))
136         persona_count++;
137
138       g_clear_object (&persona);
139     }
140
141   g_clear_object (&iter);
142
143   /* return early if these entries would add nothing beyond the "quick" items */
144   if (persona_count <= 1)
145     goto out;
146
147   /* add a separator before the list of personas */
148   item = gtk_separator_menu_item_new ();
149   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
150   gtk_widget_show (item);
151
152   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
153   while (gee_iterator_next (iter))
154     {
155       GtkWidget *image;
156       GtkWidget *contact_item;
157       GtkWidget *contact_submenu;
158       TpContact *tp_contact;
159       EmpathyContact *contact;
160       TpfPersona *persona = gee_iterator_get (iter);
161       gchar *label;
162       FolksPersonaStore *store;
163       const gchar *account;
164       GtkWidget *action;
165       /* Individual containing only persona */
166       FolksIndividual *single_individual;
167
168       if (!empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
169         goto while_finish;
170
171       tp_contact = tpf_persona_get_contact (persona);
172       if (tp_contact == NULL)
173         goto while_finish;
174
175       contact = empathy_contact_dup_from_tp_contact (tp_contact);
176       single_individual = empathy_ensure_individual_from_tp_contact (
177           tp_contact);
178
179       /* Pretty hacky. Creating single_individual had a side effect to change
180        * persona.individual from individual to single_individual which is not
181        * what we want so we set it back. See bgo#684971 for details. */
182       g_object_set (persona, "individual", individual, NULL);
183
184       store = folks_persona_get_store (FOLKS_PERSONA (persona));
185       account = folks_persona_store_get_display_name (store);
186
187       /* Translators: this is used in the context menu for a contact. The first
188        * parameter is a contact ID (e.g. foo@jabber.org) and the second is one
189        * of the user's account IDs (e.g. me@hotmail.com). */
190       label = g_strdup_printf (_("%s (%s)"),
191           folks_persona_get_display_id (FOLKS_PERSONA (persona)), account);
192
193       contact_item = gtk_image_menu_item_new_with_label (label);
194       gtk_image_menu_item_set_always_show_image (GTK_IMAGE_MENU_ITEM (contact_item),
195                                                  TRUE);
196       contact_submenu = gtk_menu_new ();
197       gtk_menu_item_set_submenu (GTK_MENU_ITEM (contact_item), contact_submenu);
198       image = gtk_image_new_from_icon_name (
199           empathy_icon_name_for_contact (contact), GTK_ICON_SIZE_MENU);
200       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (contact_item), image);
201       gtk_widget_show (image);
202
203       /* Chat */
204       if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
205         {
206           action = chat_menu_item_new_contact (self, contact);
207           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
208           gtk_widget_show (action);
209         }
210
211       /* SMS */
212       if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
213         {
214           action = sms_menu_item_new_contact (self, contact);
215           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
216           gtk_widget_show (action);
217         }
218
219       if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
220         {
221           /* Audio Call */
222           action = audio_call_menu_item_new_contact (self, contact);
223           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
224           gtk_widget_show (action);
225
226           /* Video Call */
227           action = video_call_menu_item_new_contact (self, contact);
228           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
229           gtk_widget_show (action);
230         }
231
232       /* Log */
233       if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
234         {
235           action = log_menu_item_new_contact (contact);
236           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
237           gtk_widget_show (action);
238         }
239
240       /* Invite */
241       action = invite_menu_item_new (NULL, contact);
242       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
243       gtk_widget_show (action);
244
245       /* File transfer */
246       if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
247         {
248           action = file_transfer_menu_item_new_contact (self, contact);
249           gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
250           gtk_widget_show (action);
251         }
252
253       /* Share my desktop */
254       action = share_my_desktop_menu_item_new_contact (self, contact);
255       gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), action);
256       gtk_widget_show (action);
257
258       /* Block */
259       if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
260           (item = block_menu_item_new_individual (single_individual))
261           != NULL) {
262         GtkWidget *sep;
263
264         sep = gtk_separator_menu_item_new ();
265         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), sep);
266         gtk_widget_show (sep);
267
268         gtk_menu_shell_append (GTK_MENU_SHELL (contact_submenu), item);
269         gtk_widget_show (item);
270       }
271
272       gtk_menu_shell_append (GTK_MENU_SHELL (menu), contact_item);
273       gtk_widget_show (contact_item);
274
275       g_free (label);
276       g_object_unref (contact);
277       g_object_unref (single_individual);
278
279 while_finish:
280       g_clear_object (&persona);
281     }
282
283 out:
284   g_clear_object (&iter);
285 }
286
287 static void
288 empathy_individual_menu_init (EmpathyIndividualMenu *self)
289 {
290   EmpathyIndividualMenuPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
291       EMPATHY_TYPE_INDIVIDUAL_MENU, EmpathyIndividualMenuPriv);
292
293   self->priv = priv;
294 }
295
296 static GList *
297 find_phone_accounts (void)
298 {
299   TpAccountManager *am;
300   GList *accounts, *l;
301   GList *found = NULL;
302
303   am = tp_account_manager_dup ();
304   g_return_val_if_fail (am != NULL, NULL);
305
306   accounts = tp_account_manager_dup_valid_accounts (am);
307   for (l = accounts; l != NULL; l = g_list_next (l))
308     {
309       TpAccount *account = l->data;
310
311       if (tp_account_get_connection_status (account, NULL) !=
312           TP_CONNECTION_STATUS_CONNECTED)
313         continue;
314
315       if (!tp_account_associated_with_uri_scheme (account, "tel"))
316         continue;
317
318       found = g_list_prepend (found, g_object_ref (account));
319     }
320
321   g_list_free_full (accounts, g_object_unref);
322   g_object_unref (am);
323
324   return found;
325 }
326
327 static gboolean
328 has_phone_account (void)
329 {
330   GList *accounts;
331   gboolean result;
332
333   accounts = find_phone_accounts ();
334   result = (accounts != NULL);
335
336   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
337
338   return result;
339 }
340
341 static void
342 call_phone_number (FolksPhoneFieldDetails *details,
343     TpAccount *account)
344 {
345   gchar *number;
346
347   number = folks_phone_field_details_get_normalised (details);
348   DEBUG ("Try to call %s", number);
349
350   empathy_call_new_with_streams (number,
351       account, FALSE, empathy_get_current_action_time ());
352
353   g_free (number);
354 }
355
356 static void
357 display_call_phone_dialog (FolksPhoneFieldDetails *details,
358     GList *accounts)
359 {
360   GtkWidget *dialog;
361   gint response;
362
363   dialog = empathy_account_selector_dialog_new (accounts);
364
365   gtk_window_set_title (GTK_WINDOW (dialog),
366       _("Select account to use to place the call"));
367
368   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
369       GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
370       _("Call"), GTK_RESPONSE_OK,
371       NULL);
372
373   response = gtk_dialog_run (GTK_DIALOG (dialog));
374
375   if (response == GTK_RESPONSE_OK)
376     {
377       TpAccount *account;
378
379       account = empathy_account_selector_dialog_dup_selected (
380            EMPATHY_ACCOUNT_SELECTOR_DIALOG (dialog));
381
382       if (account != NULL)
383         {
384           call_phone_number (details, account);
385
386           g_object_unref (account);
387         }
388     }
389
390   gtk_widget_destroy (dialog);
391 }
392
393 static void
394 call_phone_number_cb (GtkMenuItem *item,
395       FolksPhoneFieldDetails *details)
396 {
397   GList *accounts;
398
399   accounts = find_phone_accounts ();
400   if (accounts == NULL)
401     {
402       DEBUG ("No phone aware account connected; can't call");
403     }
404   else if (g_list_length (accounts) == 1)
405     {
406       call_phone_number (details, accounts->data);
407     }
408   else
409     {
410       /* Ask which account to use */
411       display_call_phone_dialog (details, accounts);
412     }
413
414   g_list_free_full (accounts, (GDestroyNotify) g_object_unref);
415 }
416
417 static const gchar *
418 find_phone_type (FolksPhoneFieldDetails *details)
419 {
420   GeeCollection *types;
421   GeeIterator *iter;
422   const gchar *retval = NULL;
423
424   types = folks_abstract_field_details_get_parameter_values (
425       FOLKS_ABSTRACT_FIELD_DETAILS (details), "type");
426
427   if (types == NULL)
428     return NULL;
429
430   iter = gee_iterable_iterator (GEE_ITERABLE (types));
431   while (gee_iterator_next (iter))
432     {
433       gchar *type = gee_iterator_get (iter);
434
435       if (!tp_strdiff (type, "CELL"))
436         retval = _("Mobile");
437       else if (!tp_strdiff (type, "WORK"))
438         retval = _("Work");
439       else if (!tp_strdiff (type, "HOME"))
440         retval = _("HOME");
441
442       g_free (type);
443
444       if (retval != NULL)
445         break;
446     }
447
448   g_object_unref (iter);
449
450   return retval;
451 }
452
453 static void
454 add_phone_numbers (EmpathyIndividualMenu *self)
455 {
456   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
457   GeeSet *all_numbers;
458   GeeIterator *iter;
459   gboolean sensitive;
460
461   all_numbers = folks_phone_details_get_phone_numbers (
462       FOLKS_PHONE_DETAILS (priv->individual));
463
464   sensitive = has_phone_account ();
465
466   iter = gee_iterable_iterator (GEE_ITERABLE (all_numbers));
467   while (gee_iterator_next (iter))
468     {
469       FolksPhoneFieldDetails *details = gee_iterator_get (iter);
470       GtkWidget *item, *image;
471       gchar *tmp, *number;
472       const gchar *type;
473
474       type = find_phone_type (details);
475       number = folks_phone_field_details_get_normalised (details);
476
477       if (type != NULL)
478         {
479           /* translators: first argument is a phone number like +32123456 and
480            * the second one is something like 'home' or 'work'. */
481           tmp = g_strdup_printf (_("Call %s (%s)"), number, type);
482         }
483       else
484         {
485           /* translators: argument is a phone number like +32123456 */
486           tmp = g_strdup_printf (_("Call %s"), number);
487         }
488
489       g_free (number);
490
491       item = gtk_image_menu_item_new_with_mnemonic (tmp);
492       g_free (tmp);
493
494       g_signal_connect_data (item, "activate",
495           G_CALLBACK (call_phone_number_cb), g_object_ref (details),
496           (GClosureNotify) g_object_unref, 0);
497
498       gtk_widget_set_sensitive (item, sensitive);
499
500       image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CALL,
501           GTK_ICON_SIZE_MENU);
502       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
503       gtk_widget_show (image);
504
505       gtk_menu_shell_append (GTK_MENU_SHELL (self), item);
506       gtk_widget_show (item);
507
508       g_object_unref (details);
509     }
510
511   g_object_unref (iter);
512 }
513
514 /* return a list of TpContact supporting the blocking iface */
515 static GList *
516 get_contacts_supporting_blocking (FolksIndividual *individual)
517 {
518   GeeSet *personas;
519   GeeIterator *iter;
520   GList *result = NULL;
521
522   personas = folks_individual_get_personas (individual);
523
524   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
525   while (gee_iterator_next (iter))
526     {
527       TpfPersona *persona = gee_iterator_get (iter);
528       TpContact *contact;
529       TpConnection *conn;
530
531       if (!TPF_IS_PERSONA (persona))
532         goto while_next;
533
534       contact = tpf_persona_get_contact (persona);
535       if (contact == NULL)
536         goto while_next;
537
538       conn = tp_contact_get_connection (contact);
539
540       if (tp_proxy_has_interface_by_id (conn,
541         TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_BLOCKING))
542         result = g_list_prepend (result, contact);
543
544 while_next:
545       g_clear_object (&persona);
546     }
547
548   g_clear_object (&iter);
549
550   return result;
551 }
552
553 typedef struct
554 {
555   gboolean blocked;
556   GtkWidget *parent;
557 } GotAvatarCtx;
558
559 static GotAvatarCtx *
560 got_avatar_ctx_new (gboolean blocked,
561     GtkWidget *parent)
562 {
563   GotAvatarCtx *ctx = g_slice_new0 (GotAvatarCtx);
564
565   ctx->blocked = blocked;
566   ctx->parent = parent != NULL ? g_object_ref (parent) : NULL;
567   return ctx;
568 }
569
570 static void
571 got_avatar_ctx_free (GotAvatarCtx *ctx)
572 {
573   g_clear_object (&ctx->parent);
574   g_slice_free (GotAvatarCtx, ctx);
575 }
576
577 static void
578 got_avatar (GObject *source_object,
579     GAsyncResult *result,
580     gpointer user_data)
581 {
582   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
583   GotAvatarCtx *ctx = user_data;
584   GdkPixbuf *avatar;
585   GError *error = NULL;
586   gboolean abusive = FALSE;
587   EmpathyIndividualManager *manager;
588
589   avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
590       result, &error);
591
592   if (error != NULL)
593     {
594       DEBUG ("Could not get avatar: %s", error->message);
595       g_error_free (error);
596     }
597
598   if (ctx->blocked) {
599     /* confirm the user really wishes to block the contact */
600     if (!empathy_block_individual_dialog_show (GTK_WINDOW (ctx->parent),
601           individual, avatar, &abusive))
602       goto out;
603   }
604
605   manager = empathy_individual_manager_dup_singleton ();
606
607   empathy_individual_manager_set_blocked (manager, individual,
608       ctx->blocked, abusive);
609
610   g_object_unref (manager);
611
612 out:
613   g_clear_object (&avatar);
614   got_avatar_ctx_free (ctx);
615 }
616
617 static void
618 empathy_individual_block_menu_item_toggled (GtkCheckMenuItem *item,
619     FolksIndividual *individual)
620 {
621   GotAvatarCtx *ctx;
622   gboolean blocked;
623   GtkWidget *parent;
624
625   /* @item may be destroyed while the async call is running to get the things
626    * we need from it right now. */
627   blocked = gtk_check_menu_item_get_active (item);
628
629   parent = g_object_get_data (
630     G_OBJECT (gtk_widget_get_parent (GTK_WIDGET (item))),
631     "window");
632
633   ctx = got_avatar_ctx_new (blocked, parent);
634
635   empathy_pixbuf_avatar_from_individual_scaled_async (individual,
636       48, 48, NULL, got_avatar, ctx);
637 }
638
639 static void
640 update_block_menu_item (GtkWidget *item,
641     FolksIndividual *individual)
642 {
643   GList *contacts, *l;
644   gboolean is_blocked = TRUE;
645
646   contacts = get_contacts_supporting_blocking (individual);
647
648   if (contacts == NULL)
649     is_blocked = FALSE;
650
651   /* Check the menu item if all his personas are blocked */
652   for (l = contacts; l != NULL; l = g_list_next (l))
653     {
654       TpContact *contact = l->data;
655
656       if (!tp_contact_is_blocked (contact))
657         {
658           is_blocked = FALSE;
659           break;
660         }
661     }
662
663   g_signal_handlers_block_by_func (item,
664       empathy_individual_block_menu_item_toggled, individual);
665
666   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), is_blocked);
667
668   g_signal_handlers_unblock_by_func (item,
669       empathy_individual_block_menu_item_toggled, individual);
670
671   g_list_free (contacts);
672 }
673
674 static void
675 contact_blocked_changed_cb (TpContact *contact,
676     GParamSpec *spec,
677     GtkWidget *item)
678 {
679   FolksIndividual *individual;
680
681   individual = g_object_get_data (G_OBJECT (item), "individual");
682
683   update_block_menu_item (item, individual);
684 }
685
686 static GtkWidget *
687 block_menu_item_new_individual (FolksIndividual *individual)
688 {
689   GtkWidget *item;
690   GList *contacts, *l;
691
692   contacts = get_contacts_supporting_blocking (individual);
693
694   /* Can't block, no persona supports blocking */
695   if (contacts == NULL)
696     return NULL;
697
698   item = gtk_check_menu_item_new_with_mnemonic (_("_Block Contact"));
699
700   g_object_set_data_full (G_OBJECT (item), "individual",
701       g_object_ref (individual), g_object_unref);
702
703   for (l = contacts; l != NULL; l = g_list_next (l))
704     {
705       TpContact *contact = l->data;
706
707       tp_g_signal_connect_object (contact, "notify::is-blocked",
708           G_CALLBACK (contact_blocked_changed_cb), item, 0);
709     }
710
711   g_signal_connect (item, "toggled",
712       G_CALLBACK (empathy_individual_block_menu_item_toggled), individual);
713
714   update_block_menu_item (item, individual);
715
716   g_list_free (contacts);
717
718   return item;
719 }
720
721 enum
722 {
723   REMOVE_DIALOG_RESPONSE_CANCEL = 0,
724   REMOVE_DIALOG_RESPONSE_DELETE,
725   REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK,
726   REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP
727 };
728
729 static int
730 remove_dialog_show (const gchar *message,
731     const gchar *secondary_text,
732     gboolean show_remove_from_group,
733     gboolean block_button,
734     GdkPixbuf *avatar,
735     const gchar *active_group)
736 {
737   GtkWidget *dialog;
738   gboolean res;
739
740   dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
741       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, "%s", message);
742
743   if (avatar != NULL)
744     {
745       GtkWidget *image = gtk_image_new_from_pixbuf (avatar);
746       gtk_message_dialog_set_image (GTK_MESSAGE_DIALOG (dialog), image);
747       gtk_widget_show (image);
748     }
749
750   if (show_remove_from_group)
751     {
752       GtkWidget *button;
753       gchar *button_text = g_strdup_printf (_("Remove from _Group \'%s\'"),
754           active_group);
755
756       /* gtk_dialog_add_button() doesn't allow us to pass a string with a
757        * mnemonic so we have to create the button manually. */
758       button = gtk_button_new_with_mnemonic (button_text);
759       g_free (button_text);
760
761       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
762           REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP);
763
764       gtk_widget_show (button);
765     }
766
767   if (block_button)
768     {
769       GtkWidget *button;
770
771       /* gtk_dialog_add_button() doesn't allow us to pass a string with a
772        * mnemonic so we have to create the button manually. */
773       button = gtk_button_new_with_mnemonic (
774           _("Delete and _Block"));
775
776       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
777           REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
778
779       gtk_widget_show (button);
780     }
781
782   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
783       GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
784       GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
785   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
786       "%s", secondary_text);
787
788   gtk_widget_show (dialog);
789
790   res = gtk_dialog_run (GTK_DIALOG (dialog));
791   gtk_widget_destroy (dialog);
792
793   return res;
794 }
795
796 static void
797 individual_removed_from_group_cb (GObject *source_object,
798     GAsyncResult *res,
799     gpointer user_data)
800 {
801   GError *error = NULL;
802   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
803
804   folks_group_details_change_group_finish (
805       FOLKS_GROUP_DETAILS (individual), res, &error);
806   if (error != NULL)
807     {
808       DEBUG ("Individual could not be removed from group: %s",
809           error->message);
810       g_error_free (error);
811     }
812 }
813
814 static void
815 remove_got_avatar (GObject *source_object,
816     GAsyncResult *result,
817     gpointer user_data)
818 {
819   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
820   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (user_data);
821   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
822   GdkPixbuf *avatar;
823   EmpathyIndividualManager *manager;
824   gchar *text;
825   GeeSet *personas;
826   guint persona_count = 0;
827   gboolean can_block;
828   GError *error = NULL;
829   gint res;
830   gboolean show_remove_from_group;
831   GeeSet *groups;
832
833   avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
834       result, &error);
835
836   if (error != NULL)
837     {
838       DEBUG ("Could not get avatar: %s", error->message);
839       g_error_free (error);
840     }
841
842   /* We couldn't retrieve the avatar, but that isn't a fatal error,
843    * so we still display the remove dialog. */
844
845   groups = folks_group_details_get_groups (FOLKS_GROUP_DETAILS (individual));
846   show_remove_from_group =
847       gee_collection_get_size (GEE_COLLECTION (groups)) > 1;
848
849   personas = folks_individual_get_personas (individual);
850
851   persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
852
853   /* If we have more than one TpfPersona, display a different message
854    * ensuring the user knows that *all* of the meta-contacts' personas will
855    * be removed. */
856
857   if (persona_count < 2)
858     {
859       /* Not a meta-contact */
860       text =
861           g_strdup_printf (
862               _("Do you really want to remove the contact '%s'?"),
863               folks_alias_details_get_alias (
864                   FOLKS_ALIAS_DETAILS (individual)));
865     }
866   else
867     {
868       /* Meta-contact */
869       text =
870           g_strdup_printf (
871               _("Do you really want to remove the linked contact '%s'? "
872                 "Note that this will remove all the contacts which make up "
873                 "this linked contact."),
874               folks_alias_details_get_alias (
875                   FOLKS_ALIAS_DETAILS (individual)));
876     }
877
878
879   manager = empathy_individual_manager_dup_singleton ();
880   can_block = empathy_individual_manager_supports_blocking (manager,
881       individual);
882   res = remove_dialog_show (_("Removing contact"), text,
883       show_remove_from_group, can_block, avatar, priv->active_group);
884
885   if (res == REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP)
886     {
887       folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
888           priv->active_group, false, individual_removed_from_group_cb, NULL);
889       goto finally;
890     }
891
892   if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
893       res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
894     {
895       gboolean abusive;
896
897       if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
898         {
899           if (!empathy_block_individual_dialog_show (NULL, individual,
900                 avatar, &abusive))
901             goto finally;
902
903           empathy_individual_manager_set_blocked (manager, individual,
904               TRUE, abusive);
905         }
906
907       empathy_individual_manager_remove (manager, individual, "");
908     }
909
910  finally:
911   g_free (text);
912   g_object_unref (manager);
913   g_object_unref (self);
914 }
915
916 static void
917 remove_activate_cb (GtkMenuItem *menuitem,
918     EmpathyIndividualMenu *self)
919 {
920   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
921
922   empathy_pixbuf_avatar_from_individual_scaled_async (priv->individual,
923       48, 48, NULL, remove_got_avatar, g_object_ref (self));
924 }
925
926 static GtkWidget *
927 remove_menu_item_new_individual (EmpathyIndividualMenu *self)
928 {
929   GeeSet *personas;
930   GeeIterator *iter;
931   gboolean can_remove = FALSE;
932   GtkWidget *item, *image;
933   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
934
935   /* If any of the Individual's personas can be removed, add an option to
936    * remove. This will act as a best-effort option. If any Personas cannot be
937    * removed from the server, then this option will just be inactive upon
938    * subsequent menu openings */
939   personas = folks_individual_get_personas (priv->individual);
940   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
941   while (!can_remove && gee_iterator_next (iter))
942     {
943       FolksPersona *persona = gee_iterator_get (iter);
944       FolksPersonaStore *store = folks_persona_get_store (persona);
945       FolksMaybeBool maybe_can_remove =
946           folks_persona_store_get_can_remove_personas (store);
947
948       if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
949         can_remove = TRUE;
950
951       g_clear_object (&persona);
952     }
953   g_clear_object (&iter);
954
955   if (!can_remove)
956     return NULL;
957
958   item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
959   image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
960       GTK_ICON_SIZE_MENU);
961   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
962
963   g_signal_connect (item, "activate",
964       G_CALLBACK (remove_activate_cb), self);
965
966   return item;
967 }
968
969 static void
970 constructed (GObject *object)
971 {
972   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (object);
973   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
974   GtkMenuShell *shell;
975   GtkWidget *item;
976   FolksIndividual *individual;
977   EmpathyIndividualFeatureFlags features;
978
979   /* Build the menu */
980   shell = GTK_MENU_SHELL (object);
981   individual = priv->individual;
982   features = priv->features;
983
984   /* Add contact */
985   if (features & EMPATHY_INDIVIDUAL_FEATURE_ADD_CONTACT)
986     {
987       item = add_menu_item_new_individual (self, individual);
988       if (item != NULL)
989         {
990           gtk_menu_shell_append (GTK_MENU_SHELL (shell), item);
991           gtk_widget_show (item);
992         }
993     }
994
995   /* Chat */
996   if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
997     {
998       item = chat_menu_item_new_individual (self, individual);
999       if (item != NULL)
1000         {
1001           gtk_menu_shell_append (shell, item);
1002           gtk_widget_show (item);
1003         }
1004     }
1005
1006   /* SMS */
1007   if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
1008     {
1009       item = sms_menu_item_new_individual (self, individual);
1010       if (item != NULL)
1011         {
1012           gtk_menu_shell_append (shell, item);
1013           gtk_widget_show (item);
1014         }
1015     }
1016
1017   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
1018     {
1019       /* Audio Call */
1020       item = empathy_individual_audio_call_menu_item_new_individual (self,
1021           individual);
1022       gtk_menu_shell_append (shell, item);
1023       gtk_widget_show (item);
1024
1025       /* Video Call */
1026       item = empathy_individual_video_call_menu_item_new_individual (self,
1027           individual);
1028       gtk_menu_shell_append (shell, item);
1029       gtk_widget_show (item);
1030     }
1031
1032   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE)
1033     add_phone_numbers (self);
1034
1035   /* Invite */
1036   item = invite_menu_item_new (individual, NULL);
1037   gtk_menu_shell_append (shell, item);
1038   gtk_widget_show (item);
1039
1040   /* File transfer */
1041   if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
1042     {
1043       item = file_transfer_menu_item_new_individual (self, individual);
1044       gtk_menu_shell_append (shell, item);
1045       gtk_widget_show (item);
1046     }
1047
1048   /* Share my desktop */
1049   /* FIXME we should add the "Share my desktop" menu item if Vino is
1050   a registered handler in MC5 */
1051   item = share_my_desktop_menu_item_new_individual (self, individual);
1052   gtk_menu_shell_append (shell, item);
1053   gtk_widget_show (item);
1054
1055   /* Menu items to target specific contacts */
1056   individual_menu_add_personas (self, GTK_MENU_SHELL (object),
1057       individual, features);
1058
1059   /* Separator */
1060   if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1061       EMPATHY_INDIVIDUAL_FEATURE_INFO |
1062       EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE))
1063     {
1064       item = gtk_separator_menu_item_new ();
1065       gtk_menu_shell_append (shell, item);
1066       gtk_widget_show (item);
1067     }
1068
1069   /* Edit */
1070   if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
1071     {
1072       item = edit_menu_item_new_individual (individual);
1073       gtk_menu_shell_append (shell, item);
1074       gtk_widget_show (item);
1075     }
1076
1077   /* Log */
1078   if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
1079     {
1080       item = log_menu_item_new_individual (individual);
1081       gtk_menu_shell_append (shell, item);
1082       gtk_widget_show (item);
1083     }
1084
1085   /* Info */
1086   if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
1087     {
1088       item = info_menu_item_new_individual (individual);
1089       gtk_menu_shell_append (shell, item);
1090       gtk_widget_show (item);
1091     }
1092
1093   /* Favorite checkbox */
1094   if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
1095     {
1096       item = favourite_menu_item_new_individual (individual);
1097       gtk_menu_shell_append (shell, item);
1098       gtk_widget_show (item);
1099     }
1100
1101   /* Separator & Block */
1102   if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
1103       (item = block_menu_item_new_individual (individual)) != NULL) {
1104     GtkWidget *sep;
1105
1106     sep = gtk_separator_menu_item_new ();
1107     gtk_menu_shell_append (shell, sep);
1108     gtk_widget_show (sep);
1109
1110     gtk_menu_shell_append (shell, item);
1111     gtk_widget_show (item);
1112   }
1113
1114   /* Separator & Remove */
1115   if (features & EMPATHY_INDIVIDUAL_FEATURE_REMOVE &&
1116       (item = remove_menu_item_new_individual (self)) != NULL) {
1117     GtkWidget *sep;
1118
1119     sep = gtk_separator_menu_item_new ();
1120     gtk_menu_shell_append (shell, sep);
1121     gtk_widget_show (sep);
1122
1123     gtk_menu_shell_append (shell, item);
1124     gtk_widget_show (item);
1125   }
1126 }
1127
1128 static void
1129 get_property (GObject *object,
1130     guint param_id,
1131     GValue *value,
1132     GParamSpec *pspec)
1133 {
1134   EmpathyIndividualMenuPriv *priv;
1135
1136   priv = GET_PRIV (object);
1137
1138   switch (param_id)
1139     {
1140       case PROP_ACTIVE_GROUP:
1141         g_value_set_string (value, priv->active_group);
1142         break;
1143       case PROP_INDIVIDUAL:
1144         g_value_set_object (value, priv->individual);
1145         break;
1146       case PROP_FEATURES:
1147         g_value_set_flags (value, priv->features);
1148         break;
1149       case PROP_STORE:
1150         g_value_set_object (value, priv->store);
1151         break;
1152       default:
1153         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1154         break;
1155     }
1156 }
1157
1158 static void
1159 set_property (GObject *object,
1160     guint param_id,
1161     const GValue *value,
1162     GParamSpec *pspec)
1163 {
1164   EmpathyIndividualMenuPriv *priv;
1165
1166   priv = GET_PRIV (object);
1167
1168   switch (param_id)
1169     {
1170       case PROP_ACTIVE_GROUP:
1171         g_assert (priv->active_group == NULL); /* construct only */
1172         priv->active_group = g_value_dup_string (value);
1173         break;
1174       case PROP_INDIVIDUAL:
1175         priv->individual = g_value_dup_object (value);
1176         break;
1177       case PROP_FEATURES:
1178         priv->features = g_value_get_flags (value);
1179         break;
1180       case PROP_STORE:
1181         priv->store = g_value_dup_object (value); /* read only */
1182         break;
1183       default:
1184         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1185         break;
1186     }
1187 }
1188
1189 static void
1190 dispose (GObject *object)
1191 {
1192   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1193
1194   tp_clear_object (&priv->individual);
1195   tp_clear_object (&priv->store);
1196
1197   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
1198 }
1199
1200 static void
1201 finalize (GObject *object)
1202 {
1203   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1204
1205   g_free (priv->active_group);
1206
1207   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->finalize (object);
1208 }
1209
1210 static void
1211 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
1212 {
1213   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1214
1215   object_class->constructed = constructed;
1216   object_class->get_property = get_property;
1217   object_class->set_property = set_property;
1218   object_class->dispose = dispose;
1219   object_class->finalize = finalize;
1220
1221   /**
1222    * gchar *:active-group:
1223    *
1224    * The group the selected roster-contact widget belongs, or NULL.
1225    */
1226   g_object_class_install_property (object_class, PROP_ACTIVE_GROUP,
1227       g_param_spec_string ("active-group",
1228           "Active group",
1229           "The group the selected roster-contact widget belongs, or NULL",
1230           NULL,
1231           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1232
1233   /**
1234    * EmpathyIndividualMenu:individual:
1235    *
1236    * The #FolksIndividual the menu is for.
1237    */
1238   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
1239       g_param_spec_object ("individual",
1240           "Individual",
1241           "The #FolksIndividual the menu is for.",
1242           FOLKS_TYPE_INDIVIDUAL,
1243           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1244
1245   /**
1246    * EmpathyIndividualMenu:features:
1247    *
1248    * A set of feature flags controlling which entries are shown.
1249    */
1250   g_object_class_install_property (object_class, PROP_FEATURES,
1251       g_param_spec_flags ("features",
1252           "Features",
1253           "A set of feature flags controlling which entries are shown.",
1254           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1255           EMPATHY_INDIVIDUAL_FEATURE_NONE,
1256           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1257
1258   g_object_class_install_property (object_class, PROP_STORE,
1259       g_param_spec_object ("store",
1260           "Store",
1261           "The EmpathyIndividualStore to use to get contact owner",
1262           EMPATHY_TYPE_INDIVIDUAL_STORE,
1263           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1264
1265   signals[MENU_ITEM_ACTIVATED] =
1266       g_signal_new ("menu-item-activated",
1267           G_TYPE_FROM_CLASS (klass),
1268           G_SIGNAL_RUN_LAST,
1269           0,
1270           NULL, NULL,
1271           g_cclosure_marshal_generic,
1272           G_TYPE_NONE,
1273           0);
1274
1275   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
1276 }
1277
1278 GtkWidget *
1279 empathy_individual_menu_new (FolksIndividual *individual,
1280     const gchar *active_group,
1281     EmpathyIndividualFeatureFlags features,
1282     EmpathyIndividualStore *store)
1283 {
1284   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1285   g_return_val_if_fail (store == NULL ||
1286       EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1287   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
1288
1289   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
1290       "active-group", active_group,
1291       "individual", individual,
1292       "features", features,
1293       "store", store,
1294       NULL);
1295 }
1296
1297 /* Like menu_item_set_first_contact(), but always operates upon the given
1298  * contact. If the contact is non-NULL, it is assumed that the menu entry should
1299  * be sensitive. */
1300 static gboolean
1301 menu_item_set_contact (GtkWidget *item,
1302     EmpathyContact *contact,
1303     GCallback activate_callback,
1304     EmpathyActionType action_type)
1305 {
1306   gboolean can_do_action = FALSE;
1307
1308   if (contact != NULL)
1309     can_do_action = empathy_contact_can_do_action (contact, action_type);
1310   gtk_widget_set_sensitive (item, can_do_action);
1311
1312   if (can_do_action == TRUE)
1313     {
1314       /* We want to make sure that the EmpathyContact stays alive while the
1315        * signal is connected. */
1316       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
1317           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
1318     }
1319
1320   return can_do_action;
1321 }
1322
1323 /**
1324  * Set the given menu @item to call @activate_callback using the TpContact
1325  * (associated with @individual) with the highest availability who is also valid
1326  * whenever @item is activated.
1327  *
1328  * @action_type is the type of action performed by the menu entry; this is used
1329  * so that only contacts which can perform that action (e.g. are capable of
1330  * receiving video calls) are selected, as appropriate.
1331  */
1332 static GtkWidget *
1333 menu_item_set_first_contact (GtkWidget *item,
1334     FolksIndividual *individual,
1335     GCallback activate_callback,
1336     EmpathyActionType action_type)
1337 {
1338   EmpathyContact *best_contact;
1339
1340   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
1341   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
1342       action_type);
1343   tp_clear_object (&best_contact);
1344
1345   return item;
1346 }
1347
1348 static void
1349 emit_menu_item_activated (GtkMenuItem *item)
1350 {
1351   EmpathyIndividualMenu *self;
1352
1353   self = EMPATHY_INDIVIDUAL_MENU (g_object_get_data (G_OBJECT (item),
1354       "individual-menu"));
1355   g_signal_emit (self, signals [MENU_ITEM_ACTIVATED], 0);
1356 }
1357
1358 static void
1359 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
1360   EmpathyContact *contact)
1361 {
1362   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1363
1364   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
1365
1366   emit_menu_item_activated (item);
1367 }
1368
1369 static GtkWidget *
1370 chat_menu_item_new (EmpathyIndividualMenu *self)
1371 {
1372   GtkWidget *item;
1373   GtkWidget *image;
1374
1375   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
1376   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
1377       GTK_ICON_SIZE_MENU);
1378   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1379   gtk_widget_show (image);
1380
1381   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1382
1383   return item;
1384 }
1385
1386 static GtkWidget *
1387 chat_menu_item_new_individual (EmpathyIndividualMenu *self,
1388     FolksIndividual *individual)
1389 {
1390   GtkWidget *item;
1391
1392   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1393       empathy_folks_individual_contains_contact (individual), NULL);
1394
1395   item = chat_menu_item_new (self);
1396
1397   menu_item_set_first_contact (item, individual,
1398       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1399       EMPATHY_ACTION_CHAT);
1400
1401   return item;
1402 }
1403
1404 static GtkWidget *
1405 chat_menu_item_new_contact (EmpathyIndividualMenu *self,
1406     EmpathyContact *contact)
1407 {
1408   GtkWidget *item;
1409
1410   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1411
1412   item = chat_menu_item_new (self);
1413
1414   menu_item_set_contact (item, contact,
1415       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1416       EMPATHY_ACTION_CHAT);
1417
1418   return item;
1419 }
1420
1421 static void
1422 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
1423   EmpathyContact *contact)
1424 {
1425   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1426
1427   empathy_sms_contact_id (
1428       empathy_contact_get_account (contact),
1429       empathy_contact_get_id (contact),
1430       empathy_get_current_action_time (),
1431       NULL, NULL);
1432
1433   emit_menu_item_activated (item);
1434 }
1435
1436 static GtkWidget *
1437 sms_menu_item_new (EmpathyIndividualMenu *self)
1438 {
1439   GtkWidget *item;
1440   GtkWidget *image;
1441
1442   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
1443   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
1444       GTK_ICON_SIZE_MENU);
1445   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1446   gtk_widget_show (image);
1447
1448   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1449
1450   return item;
1451 }
1452
1453 static GtkWidget *
1454 sms_menu_item_new_individual (EmpathyIndividualMenu *self,
1455     FolksIndividual *individual)
1456 {
1457   GtkWidget *item;
1458
1459   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1460       empathy_folks_individual_contains_contact (individual), NULL);
1461
1462   item = sms_menu_item_new (self);
1463
1464   menu_item_set_first_contact (item, individual,
1465       G_CALLBACK (empathy_individual_sms_menu_item_activated),
1466       EMPATHY_ACTION_SMS);
1467
1468   return item;
1469 }
1470
1471 static GtkWidget *
1472 sms_menu_item_new_contact (EmpathyIndividualMenu *self,
1473     EmpathyContact *contact)
1474 {
1475   GtkWidget *item;
1476
1477   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1478
1479   item = sms_menu_item_new (self);
1480
1481   menu_item_set_contact (item, contact,
1482       G_CALLBACK (empathy_individual_sms_menu_item_activated),
1483       EMPATHY_ACTION_SMS);
1484
1485   return item;
1486 }
1487
1488
1489 static void
1490 empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
1491   EmpathyContact *contact)
1492 {
1493   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1494
1495   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1496       empathy_contact_get_account (contact),
1497       FALSE, empathy_get_current_action_time ());
1498
1499   emit_menu_item_activated (item);
1500 }
1501
1502 static GtkWidget *
1503 audio_call_menu_item_new (EmpathyIndividualMenu *self)
1504 {
1505   GtkWidget *item;
1506   GtkWidget *image;
1507
1508   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
1509   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1510   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1511   gtk_widget_show (image);
1512
1513   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1514
1515   return item;
1516 }
1517
1518 GtkWidget *
1519 empathy_individual_audio_call_menu_item_new_individual (
1520     EmpathyIndividualMenu *self,
1521     FolksIndividual *individual)
1522 {
1523   GtkWidget *item;
1524
1525   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1526
1527   item = audio_call_menu_item_new (self);
1528
1529   menu_item_set_first_contact (item, individual,
1530       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1531       EMPATHY_ACTION_AUDIO_CALL);
1532
1533   return item;
1534 }
1535
1536 static GtkWidget *
1537 audio_call_menu_item_new_contact (
1538     EmpathyIndividualMenu *self,
1539     EmpathyContact *contact)
1540 {
1541   GtkWidget *item;
1542
1543   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1544
1545   item = audio_call_menu_item_new (self);
1546
1547   menu_item_set_contact (item, contact,
1548       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1549       EMPATHY_ACTION_AUDIO_CALL);
1550
1551   return item;
1552 }
1553
1554
1555 static void
1556 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1557   EmpathyContact *contact)
1558 {
1559   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1560
1561   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1562       empathy_contact_get_account (contact),
1563       TRUE, empathy_get_current_action_time ());
1564
1565   emit_menu_item_activated (item);
1566 }
1567
1568 static GtkWidget *
1569 video_call_menu_item_new (EmpathyIndividualMenu *self)
1570 {
1571   GtkWidget *item;
1572   GtkWidget *image;
1573
1574   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1575   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1576       GTK_ICON_SIZE_MENU);
1577   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1578   gtk_widget_show (image);
1579
1580   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1581
1582   return item;
1583 }
1584
1585 static void
1586 check_camera_available (GtkWidget *item)
1587 {
1588   TpawCameraMonitor *monitor;
1589
1590   /* Only follow available cameras if the contact can do Video calls */
1591   if (gtk_widget_get_sensitive (item))
1592     {
1593       monitor = tpaw_camera_monitor_dup_singleton ();
1594       g_object_set_data_full (G_OBJECT (item),
1595           "monitor", monitor, g_object_unref);
1596       g_object_bind_property (monitor, "available", item, "sensitive",
1597           G_BINDING_SYNC_CREATE);
1598     }
1599 }
1600
1601 GtkWidget *
1602 empathy_individual_video_call_menu_item_new_individual (
1603     EmpathyIndividualMenu *self,
1604     FolksIndividual *individual)
1605 {
1606   GtkWidget *item;
1607
1608   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1609
1610   item = video_call_menu_item_new (self);
1611
1612   menu_item_set_first_contact (item, individual,
1613       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1614       EMPATHY_ACTION_VIDEO_CALL);
1615
1616   check_camera_available (item);
1617
1618   return item;
1619 }
1620
1621 GtkWidget *
1622 video_call_menu_item_new_contact (EmpathyIndividualMenu *self,
1623     EmpathyContact *contact)
1624 {
1625   GtkWidget *item;
1626
1627   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1628
1629   item = video_call_menu_item_new (self);
1630
1631   menu_item_set_contact (item, contact,
1632       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1633       EMPATHY_ACTION_VIDEO_CALL);
1634
1635   check_camera_available (item);
1636
1637   return item;
1638 }
1639
1640 static void
1641 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1642   EmpathyContact *contact)
1643 {
1644   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1645
1646   empathy_log_window_show (empathy_contact_get_account (contact),
1647       empathy_contact_get_id (contact), FALSE, NULL);
1648 }
1649
1650 static GtkWidget *
1651 log_menu_item_new (void)
1652 {
1653   GtkWidget *item;
1654   GtkWidget *image;
1655
1656   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1657   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1658   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1659   gtk_widget_show (image);
1660
1661   return item;
1662 }
1663
1664 static GtkWidget *
1665 log_menu_item_new_individual (FolksIndividual *individual)
1666 {
1667   GtkWidget *item;
1668
1669   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1670
1671   item = log_menu_item_new ();
1672
1673   menu_item_set_first_contact (item, individual,
1674       G_CALLBACK (empathy_individual_log_menu_item_activated),
1675       EMPATHY_ACTION_VIEW_LOGS);
1676
1677   return item;
1678 }
1679
1680 static GtkWidget *
1681 log_menu_item_new_contact (EmpathyContact *contact)
1682 {
1683   GtkWidget *item;
1684
1685   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1686
1687   item = log_menu_item_new ();
1688
1689   menu_item_set_contact (item, contact,
1690       G_CALLBACK (empathy_individual_log_menu_item_activated),
1691       EMPATHY_ACTION_VIEW_LOGS);
1692
1693   return item;
1694 }
1695
1696 static void
1697 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1698     EmpathyContact *contact)
1699 {
1700   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1701
1702   empathy_send_file_with_file_chooser (contact);
1703
1704   emit_menu_item_activated (item);
1705 }
1706
1707 static GtkWidget *
1708 file_transfer_menu_item_new (EmpathyIndividualMenu *self)
1709 {
1710   GtkWidget *item;
1711   GtkWidget *image;
1712
1713   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1714   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1715       GTK_ICON_SIZE_MENU);
1716   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1717   gtk_widget_show (image);
1718
1719   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1720
1721   return item;
1722 }
1723
1724 static GtkWidget *
1725 file_transfer_menu_item_new_individual (EmpathyIndividualMenu *self,
1726     FolksIndividual *individual)
1727 {
1728   GtkWidget *item;
1729
1730   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1731
1732   item = file_transfer_menu_item_new (self);
1733
1734   menu_item_set_first_contact (item, individual,
1735       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1736       EMPATHY_ACTION_SEND_FILE);
1737
1738   return item;
1739 }
1740
1741 static GtkWidget *
1742 file_transfer_menu_item_new_contact (EmpathyIndividualMenu *self,
1743     EmpathyContact *contact)
1744 {
1745   GtkWidget *item;
1746
1747   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1748
1749   item = file_transfer_menu_item_new (self);
1750
1751   menu_item_set_contact (item, contact,
1752       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1753       EMPATHY_ACTION_SEND_FILE);
1754
1755   return item;
1756 }
1757
1758 static void
1759 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1760     EmpathyContact *contact)
1761 {
1762   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1763
1764   empathy_share_my_desktop_share_with_contact (contact);
1765
1766   emit_menu_item_activated (item);
1767 }
1768
1769 static GtkWidget *
1770 share_my_desktop_menu_item_new (EmpathyIndividualMenu *self)
1771 {
1772   GtkWidget *item;
1773   GtkWidget *image;
1774
1775   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1776   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1777   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1778   gtk_widget_show (image);
1779
1780   g_object_set_data (G_OBJECT (item), "individual-menu", self);
1781
1782   return item;
1783 }
1784
1785 static GtkWidget *
1786 share_my_desktop_menu_item_new_individual (EmpathyIndividualMenu *self,
1787     FolksIndividual *individual)
1788 {
1789   GtkWidget *item;
1790
1791   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1792
1793   item = share_my_desktop_menu_item_new (self);
1794
1795   menu_item_set_first_contact (item, individual,
1796       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1797       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1798
1799   return item;
1800 }
1801
1802 static GtkWidget *
1803 share_my_desktop_menu_item_new_contact (EmpathyIndividualMenu *self,
1804     EmpathyContact *contact)
1805 {
1806   GtkWidget *item;
1807
1808   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1809
1810   item = share_my_desktop_menu_item_new (self);
1811
1812   menu_item_set_contact (item, contact,
1813       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1814       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1815
1816   return item;
1817 }
1818
1819 static void
1820 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1821   FolksIndividual *individual)
1822 {
1823   folks_favourite_details_set_is_favourite (
1824       FOLKS_FAVOURITE_DETAILS (individual),
1825       gtk_check_menu_item_get_active (item));
1826 }
1827
1828 static GtkWidget *
1829 favourite_menu_item_new_individual (FolksIndividual *individual)
1830 {
1831   GtkWidget *item;
1832
1833   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1834
1835   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1836       folks_favourite_details_get_is_favourite (
1837           FOLKS_FAVOURITE_DETAILS (individual)));
1838
1839   g_signal_connect (item, "toggled",
1840       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1841
1842   return item;
1843 }
1844
1845 static void
1846 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1847     FolksIndividual *individual)
1848 {
1849   empathy_display_individual_info (individual);
1850 }
1851
1852 static GtkWidget *
1853 info_menu_item_new_individual (FolksIndividual *individual)
1854 {
1855   GtkWidget *item;
1856   GtkWidget *image;
1857
1858   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1859   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1860       NULL);
1861
1862   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1863   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1864                 GTK_ICON_SIZE_MENU);
1865   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1866   gtk_widget_show (image);
1867
1868   g_signal_connect (item, "activate",
1869           G_CALLBACK (individual_info_menu_item_activate_cb),
1870           individual);
1871
1872   return item;
1873 }
1874
1875 static void
1876 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1877 {
1878   empathy_individual_edit_dialog_show (individual, NULL);
1879 }
1880
1881 static GtkWidget *
1882 edit_menu_item_new_individual (FolksIndividual *individual)
1883 {
1884   EmpathyIndividualManager *manager;
1885   GtkWidget *item;
1886   GtkWidget *image;
1887   gboolean enable = FALSE;
1888   EmpathyContact *contact;
1889
1890   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1891
1892   contact = empathy_contact_dup_from_folks_individual (individual);
1893
1894   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1895
1896   if (empathy_individual_manager_initialized ())
1897     {
1898       TpConnection *connection;
1899
1900       manager = empathy_individual_manager_dup_singleton ();
1901       connection = empathy_contact_get_connection (contact);
1902
1903       enable = (empathy_connection_can_alias_personas (connection,
1904                                                        individual) &&
1905                 empathy_connection_can_group_personas (connection, individual));
1906
1907       g_object_unref (manager);
1908     }
1909
1910   item = gtk_image_menu_item_new_with_mnemonic (
1911       C_("Edit individual (contextual menu)", "_Edit"));
1912   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1913   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1914   gtk_widget_show (image);
1915
1916   gtk_widget_set_sensitive (item, enable);
1917
1918   g_signal_connect_swapped (item, "activate",
1919       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1920
1921   g_object_unref (contact);
1922
1923   return item;
1924 }
1925
1926 typedef struct
1927 {
1928   FolksIndividual *individual;
1929   EmpathyContact *contact;
1930   EmpathyChatroom *chatroom;
1931 } RoomSubMenuData;
1932
1933 static RoomSubMenuData *
1934 room_sub_menu_data_new (FolksIndividual *individual,
1935     EmpathyContact *contact,
1936     EmpathyChatroom *chatroom)
1937 {
1938   RoomSubMenuData *data;
1939
1940   data = g_slice_new0 (RoomSubMenuData);
1941   if (individual != NULL)
1942     data->individual = g_object_ref (individual);
1943   if (contact != NULL)
1944     data->contact = g_object_ref (contact);
1945   data->chatroom = g_object_ref (chatroom);
1946
1947   return data;
1948 }
1949
1950 static void
1951 room_sub_menu_data_free (RoomSubMenuData *data)
1952 {
1953   tp_clear_object (&data->individual);
1954   tp_clear_object (&data->contact);
1955   g_object_unref (data->chatroom);
1956   g_slice_free (RoomSubMenuData, data);
1957 }
1958
1959 static void
1960 room_sub_menu_activate_cb (GtkWidget *item,
1961          RoomSubMenuData *data)
1962 {
1963   EmpathyTpChat *chat;
1964   EmpathyChatroomManager *mgr;
1965   EmpathyContact *contact = NULL;
1966
1967   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1968   if (chat == NULL)
1969     {
1970       /* channel was invalidated. Ignoring */
1971       return;
1972     }
1973
1974   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1975
1976   if (data->contact != NULL)
1977     contact = g_object_ref (data->contact);
1978   else
1979     {
1980       GeeSet *personas;
1981       GeeIterator *iter;
1982
1983       /* find the first of this Individual's contacts who can join this room */
1984       personas = folks_individual_get_personas (data->individual);
1985
1986       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1987       while (gee_iterator_next (iter) && (contact == NULL))
1988         {
1989           TpfPersona *persona = gee_iterator_get (iter);
1990           TpContact *tp_contact;
1991           GList *rooms;
1992
1993           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1994             {
1995               tp_contact = tpf_persona_get_contact (persona);
1996               if (tp_contact != NULL)
1997                 {
1998                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1999
2000                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
2001                       empathy_contact_get_account (contact));
2002
2003                   if (g_list_find (rooms, data->chatroom) == NULL)
2004                     g_clear_object (&contact);
2005
2006                   /* if contact != NULL here, we've found our match */
2007
2008                   g_list_free (rooms);
2009                 }
2010             }
2011           g_clear_object (&persona);
2012         }
2013       g_clear_object (&iter);
2014     }
2015
2016   g_object_unref (mgr);
2017
2018   if (contact == NULL)
2019     {
2020       /* contact disappeared. Ignoring */
2021       goto out;
2022     }
2023
2024   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
2025
2026   /* send invitation */
2027   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
2028
2029 out:
2030   g_object_unref (contact);
2031 }
2032
2033 static GtkWidget *
2034 create_room_sub_menu (FolksIndividual *individual,
2035                       EmpathyContact *contact,
2036                       EmpathyChatroom *chatroom)
2037 {
2038   GtkWidget *item;
2039   RoomSubMenuData *data;
2040
2041   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
2042   data = room_sub_menu_data_new (individual, contact, chatroom);
2043   g_signal_connect_data (item, "activate",
2044       G_CALLBACK (room_sub_menu_activate_cb), data,
2045       (GClosureNotify) room_sub_menu_data_free, 0);
2046
2047   return item;
2048 }
2049
2050 static GtkWidget *
2051 invite_menu_item_new (FolksIndividual *individual,
2052     EmpathyContact *contact)
2053 {
2054   GtkWidget *item;
2055   GtkWidget *image;
2056   GtkWidget *room_item;
2057   EmpathyChatroomManager *mgr;
2058   GList *rooms = NULL;
2059   GList *names = NULL;
2060   GList *l;
2061   GtkWidget *submenu = NULL;
2062   /* map of chat room names to their objects; just a utility to remove
2063    * duplicates and to make construction of the alphabetized list easier */
2064   GHashTable *name_room_map;
2065
2066   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
2067       EMPATHY_IS_CONTACT (contact),
2068       NULL);
2069
2070   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
2071       g_object_unref);
2072
2073   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
2074   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
2075       GTK_ICON_SIZE_MENU);
2076   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2077
2078   mgr = empathy_chatroom_manager_dup_singleton (NULL);
2079
2080   if (contact != NULL)
2081     {
2082       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
2083           empathy_contact_get_account (contact));
2084     }
2085   else
2086     {
2087       GeeSet *personas;
2088       GeeIterator *iter;
2089
2090       /* find the first of this Individual's contacts who can join this room */
2091       personas = folks_individual_get_personas (individual);
2092       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2093       while (gee_iterator_next (iter))
2094         {
2095           TpfPersona *persona = gee_iterator_get (iter);
2096           GList *rooms_cur;
2097           TpContact *tp_contact;
2098           EmpathyContact *contact_cur;
2099
2100           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
2101             {
2102               tp_contact = tpf_persona_get_contact (persona);
2103               if (tp_contact != NULL)
2104                 {
2105                   contact_cur = empathy_contact_dup_from_tp_contact (
2106                       tp_contact);
2107
2108                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
2109                       empathy_contact_get_account (contact_cur));
2110                   rooms = g_list_concat (rooms, rooms_cur);
2111
2112                   g_object_unref (contact_cur);
2113                 }
2114             }
2115           g_clear_object (&persona);
2116         }
2117       g_clear_object (&iter);
2118     }
2119
2120   /* alphabetize the rooms */
2121   for (l = rooms; l != NULL; l = g_list_next (l))
2122     {
2123       EmpathyChatroom *chatroom = l->data;
2124       gboolean existed;
2125
2126       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
2127         {
2128           const gchar *name;
2129
2130           name = empathy_chatroom_get_name (chatroom);
2131           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
2132           g_hash_table_insert (name_room_map, (gpointer) name,
2133               g_object_ref (chatroom));
2134
2135           /* this will take care of duplicates in rooms */
2136           if (!existed)
2137             {
2138               names = g_list_insert_sorted (names, (gpointer) name,
2139                   (GCompareFunc) g_strcmp0);
2140             }
2141         }
2142     }
2143
2144   for (l = names; l != NULL; l = g_list_next (l))
2145     {
2146       const gchar *name = l->data;
2147       EmpathyChatroom *chatroom;
2148
2149       if (G_UNLIKELY (submenu == NULL))
2150         submenu = gtk_menu_new ();
2151
2152       chatroom = g_hash_table_lookup (name_room_map, name);
2153       room_item = create_room_sub_menu (individual, contact, chatroom);
2154       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
2155       gtk_widget_show (room_item);
2156     }
2157
2158   if (submenu)
2159     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
2160   else
2161     gtk_widget_set_sensitive (item, FALSE);
2162
2163   gtk_widget_show (image);
2164
2165   g_hash_table_unref (name_room_map);
2166   g_object_unref (mgr);
2167   g_list_free (names);
2168   g_list_free (rooms);
2169
2170   return item;
2171 }
2172
2173 static void
2174 add_menu_item_activated (GtkMenuItem *item,
2175     TpContact *tp_contact)
2176 {
2177   GtkWidget *toplevel;
2178   FolksIndividual *individual;
2179
2180   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
2181   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
2182     toplevel = NULL;
2183
2184   individual = empathy_ensure_individual_from_tp_contact (tp_contact);
2185
2186   empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
2187       individual);
2188
2189   g_object_unref (individual);
2190 }
2191
2192 static GtkWidget *
2193 add_menu_item_new_individual (EmpathyIndividualMenu *self,
2194     FolksIndividual *individual)
2195 {
2196   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
2197   GtkWidget *item, *image;
2198   GeeSet *personas;
2199   GeeIterator *iter;
2200   TpContact *to_add = NULL;
2201
2202   /* find the first of this Individual's personas which are not in our contact
2203    * list. */
2204   personas = folks_individual_get_personas (individual);
2205   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
2206   while (gee_iterator_next (iter))
2207     {
2208       TpfPersona *persona = gee_iterator_get (iter);
2209       TpContact *contact;
2210       TpConnection *conn;
2211
2212       if (!TPF_IS_PERSONA (persona))
2213         goto next;
2214
2215       contact = tpf_persona_get_contact (persona);
2216       if (contact == NULL)
2217         goto next;
2218
2219       /* be sure to use a not channel specific contact.
2220        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
2221       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
2222         {
2223           TpChannel *channel;
2224           TpChannelGroupFlags flags;
2225
2226           channel = empathy_individual_store_channel_get_channel (
2227               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
2228
2229           flags = tp_channel_group_get_flags (channel);
2230           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
2231             {
2232               /* Channel uses channel specific handles (thanks XMPP...) */
2233               contact = tp_channel_group_get_contact_owner (channel, contact);
2234
2235               /* If we don't know the owner, we can't add the contact */
2236               if (contact == NULL)
2237                 goto next;
2238             }
2239         }
2240
2241       conn = tp_contact_get_connection (contact);
2242       if (conn == NULL)
2243         goto next;
2244
2245       /* No point to try adding a contact if the CM doesn't support it */
2246       if (!tp_connection_get_can_change_contact_list (conn))
2247         goto next;
2248
2249       /* Can't add ourself */
2250       if (tp_connection_get_self_contact (conn) == contact)
2251         goto next;
2252
2253       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
2254         goto next;
2255
2256       g_object_unref (persona);
2257       to_add = contact;
2258       break;
2259
2260 next:
2261       g_object_unref (persona);
2262     }
2263
2264   g_object_unref (iter);
2265
2266   if (to_add == NULL)
2267     return NULL;
2268
2269   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
2270   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
2271   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
2272
2273   g_signal_connect_data (item, "activate",
2274       G_CALLBACK (add_menu_item_activated),
2275       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
2276
2277   return item;
2278 }