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