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