]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
Show a 'Remove from Group <group-name>' button in remove dialog
[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
28 #include "empathy-account-selector-dialog.h"
29 #include "empathy-call-utils.h"
30 #include "empathy-camera-monitor.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 (!empathy_account_has_uri_scheme_tel (account))
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
724       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
725           REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP);
726
727       gtk_widget_show (button);
728     }
729
730   if (block_button)
731     {
732       GtkWidget *button;
733
734       /* gtk_dialog_add_button() doesn't allow us to pass a string with a
735        * mnemonic so we have to create the button manually. */
736       button = gtk_button_new_with_mnemonic (
737           _("Delete and _Block"));
738
739       gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
740           REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK);
741
742       gtk_widget_show (button);
743     }
744
745   gtk_dialog_add_buttons (GTK_DIALOG (dialog),
746       GTK_STOCK_CANCEL, REMOVE_DIALOG_RESPONSE_CANCEL,
747       GTK_STOCK_DELETE, REMOVE_DIALOG_RESPONSE_DELETE, NULL);
748   gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
749       "%s", secondary_text);
750
751   gtk_widget_show (dialog);
752
753   res = gtk_dialog_run (GTK_DIALOG (dialog));
754   gtk_widget_destroy (dialog);
755
756   return res;
757 }
758
759 static void
760 individual_removed_from_group_cb (GObject *source_object,
761     GAsyncResult *res,
762     gpointer user_data)
763 {
764   GError *error = NULL;
765   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
766
767   folks_group_details_change_group_finish (
768       FOLKS_GROUP_DETAILS (individual), res, &error);
769   if (error != NULL)
770     {
771       DEBUG ("Individual could not be removed from group: %s",
772           error->message);
773       g_error_free (error);
774     }
775 }
776
777 static void
778 remove_got_avatar (GObject *source_object,
779     GAsyncResult *result,
780     gpointer user_data)
781 {
782   FolksIndividual *individual = FOLKS_INDIVIDUAL (source_object);
783   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (user_data);
784   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
785   GdkPixbuf *avatar;
786   EmpathyIndividualManager *manager;
787   gchar *text;
788   GeeSet *personas;
789   guint persona_count = 0;
790   gboolean can_block;
791   GError *error = NULL;
792   gint res;
793   gboolean show_remove_from_group;
794   GeeSet *groups;
795
796   avatar = empathy_pixbuf_avatar_from_individual_scaled_finish (individual,
797       result, &error);
798
799   if (error != NULL)
800     {
801       DEBUG ("Could not get avatar: %s", error->message);
802       g_error_free (error);
803     }
804
805   /* We couldn't retrieve the avatar, but that isn't a fatal error,
806    * so we still display the remove dialog. */
807
808   groups = folks_group_details_get_groups (FOLKS_GROUP_DETAILS (individual));
809   show_remove_from_group =
810       gee_collection_get_size (GEE_COLLECTION (groups)) > 1;
811
812   personas = folks_individual_get_personas (individual);
813
814   persona_count = gee_collection_get_size (GEE_COLLECTION (personas));
815
816   /* If we have more than one TpfPersona, display a different message
817    * ensuring the user knows that *all* of the meta-contacts' personas will
818    * be removed. */
819
820   if (persona_count < 2)
821     {
822       /* Not a meta-contact */
823       text =
824           g_strdup_printf (
825               _("Do you really want to remove the contact '%s'?"),
826               folks_alias_details_get_alias (
827                   FOLKS_ALIAS_DETAILS (individual)));
828     }
829   else
830     {
831       /* Meta-contact */
832       text =
833           g_strdup_printf (
834               _("Do you really want to remove the linked contact '%s'? "
835                 "Note that this will remove all the contacts which make up "
836                 "this linked contact."),
837               folks_alias_details_get_alias (
838                   FOLKS_ALIAS_DETAILS (individual)));
839     }
840
841
842   manager = empathy_individual_manager_dup_singleton ();
843   can_block = empathy_individual_manager_supports_blocking (manager,
844       individual);
845   res = remove_dialog_show (_("Removing contact"), text,
846       show_remove_from_group, can_block, avatar, priv->active_group);
847
848   if (res == REMOVE_DIALOG_RESPONSE_REMOVE_FROM_GROUP)
849     {
850       folks_group_details_change_group (FOLKS_GROUP_DETAILS (individual),
851           priv->active_group, false, individual_removed_from_group_cb, NULL);
852       goto finally;
853     }
854
855   if (res == REMOVE_DIALOG_RESPONSE_DELETE ||
856       res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
857     {
858       gboolean abusive;
859
860       if (res == REMOVE_DIALOG_RESPONSE_DELETE_AND_BLOCK)
861         {
862           if (!empathy_block_individual_dialog_show (NULL, individual,
863                 avatar, &abusive))
864             goto finally;
865
866           empathy_individual_manager_set_blocked (manager, individual,
867               TRUE, abusive);
868         }
869
870       empathy_individual_manager_remove (manager, individual, "");
871     }
872
873  finally:
874   g_free (text);
875   g_object_unref (manager);
876   g_object_unref (self);
877 }
878
879 static void
880 remove_activate_cb (GtkMenuItem *menuitem,
881     EmpathyIndividualMenu *self)
882 {
883   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
884
885   empathy_pixbuf_avatar_from_individual_scaled_async (priv->individual,
886       48, 48, NULL, remove_got_avatar, g_object_ref (self));
887 }
888
889 static GtkWidget *
890 empathy_individiual_remove_menu_item_new (EmpathyIndividualMenu *self)
891 {
892   GeeSet *personas;
893   GeeIterator *iter;
894   gboolean can_remove = FALSE;
895   GtkWidget *item, *image;
896   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
897
898   /* If any of the Individual's personas can be removed, add an option to
899    * remove. This will act as a best-effort option. If any Personas cannot be
900    * removed from the server, then this option will just be inactive upon
901    * subsequent menu openings */
902   personas = folks_individual_get_personas (priv->individual);
903   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
904   while (!can_remove && gee_iterator_next (iter))
905     {
906       FolksPersona *persona = gee_iterator_get (iter);
907       FolksPersonaStore *store = folks_persona_get_store (persona);
908       FolksMaybeBool maybe_can_remove =
909           folks_persona_store_get_can_remove_personas (store);
910
911       if (maybe_can_remove == FOLKS_MAYBE_BOOL_TRUE)
912         can_remove = TRUE;
913
914       g_clear_object (&persona);
915     }
916   g_clear_object (&iter);
917
918   if (!can_remove)
919     return NULL;
920
921   item = gtk_image_menu_item_new_with_mnemonic (_("_Remove"));
922   image = gtk_image_new_from_icon_name (GTK_STOCK_REMOVE,
923       GTK_ICON_SIZE_MENU);
924   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
925
926   g_signal_connect (item, "activate",
927       G_CALLBACK (remove_activate_cb), self);
928
929   return item;
930 }
931
932 static void
933 constructed (GObject *object)
934 {
935   EmpathyIndividualMenu *self = EMPATHY_INDIVIDUAL_MENU (object);
936   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
937   GtkMenuShell *shell;
938   GtkWidget *item;
939   FolksIndividual *individual;
940   EmpathyIndividualFeatureFlags features;
941
942   /* Build the menu */
943   shell = GTK_MENU_SHELL (object);
944   individual = priv->individual;
945   features = priv->features;
946
947   /* Add contact */
948   if (features & EMPATHY_INDIVIDUAL_FEATURE_ADD_CONTACT)
949     {
950       item = empathy_individual_add_menu_item_new (self, individual);
951       if (item != NULL)
952         {
953           gtk_menu_shell_append (GTK_MENU_SHELL (shell), item);
954           gtk_widget_show (item);
955         }
956     }
957
958   /* Chat */
959   if (features & EMPATHY_INDIVIDUAL_FEATURE_CHAT)
960     {
961       item = empathy_individual_chat_menu_item_new (individual);
962       if (item != NULL)
963         {
964           gtk_menu_shell_append (shell, item);
965           gtk_widget_show (item);
966         }
967     }
968
969   /* SMS */
970   if (features & EMPATHY_INDIVIDUAL_FEATURE_SMS)
971     {
972       item = empathy_individual_sms_menu_item_new (individual);
973       if (item != NULL)
974         {
975           gtk_menu_shell_append (shell, item);
976           gtk_widget_show (item);
977         }
978     }
979
980   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL)
981     {
982       /* Audio Call */
983       item = empathy_individual_audio_call_menu_item_new (individual);
984       gtk_menu_shell_append (shell, item);
985       gtk_widget_show (item);
986
987       /* Video Call */
988       item = empathy_individual_video_call_menu_item_new (individual);
989       gtk_menu_shell_append (shell, item);
990       gtk_widget_show (item);
991     }
992
993   if (features & EMPATHY_INDIVIDUAL_FEATURE_CALL_PHONE)
994     add_phone_numbers (self);
995
996   /* Invite */
997   item = empathy_individual_invite_menu_item_new (individual, NULL);
998   gtk_menu_shell_append (shell, item);
999   gtk_widget_show (item);
1000
1001   /* File transfer */
1002   if (features & EMPATHY_INDIVIDUAL_FEATURE_FILE_TRANSFER)
1003     {
1004       item = empathy_individual_file_transfer_menu_item_new (individual);
1005       gtk_menu_shell_append (shell, item);
1006       gtk_widget_show (item);
1007     }
1008
1009   /* Share my desktop */
1010   /* FIXME we should add the "Share my desktop" menu item if Vino is
1011   a registered handler in MC5 */
1012   item = empathy_individual_share_my_desktop_menu_item_new (individual);
1013   gtk_menu_shell_append (shell, item);
1014   gtk_widget_show (item);
1015
1016   /* Menu items to target specific contacts */
1017   individual_menu_add_personas (GTK_MENU_SHELL (object), individual, features);
1018
1019   /* Separator */
1020   if (features & (EMPATHY_INDIVIDUAL_FEATURE_EDIT |
1021       EMPATHY_INDIVIDUAL_FEATURE_INFO |
1022       EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE))
1023     {
1024       item = gtk_separator_menu_item_new ();
1025       gtk_menu_shell_append (shell, item);
1026       gtk_widget_show (item);
1027     }
1028
1029   /* Edit */
1030   if (features & EMPATHY_INDIVIDUAL_FEATURE_EDIT)
1031     {
1032       item = empathy_individual_edit_menu_item_new (individual);
1033       gtk_menu_shell_append (shell, item);
1034       gtk_widget_show (item);
1035     }
1036
1037   /* Log */
1038   if (features & EMPATHY_INDIVIDUAL_FEATURE_LOG)
1039     {
1040       item = empathy_individual_log_menu_item_new (individual);
1041       gtk_menu_shell_append (shell, item);
1042       gtk_widget_show (item);
1043     }
1044
1045   /* Info */
1046   if (features & EMPATHY_INDIVIDUAL_FEATURE_INFO)
1047     {
1048       item = empathy_individual_info_menu_item_new (individual);
1049       gtk_menu_shell_append (shell, item);
1050       gtk_widget_show (item);
1051     }
1052
1053   /* Favorite checkbox */
1054   if (features & EMPATHY_INDIVIDUAL_FEATURE_FAVOURITE)
1055     {
1056       item = empathy_individual_favourite_menu_item_new (individual);
1057       gtk_menu_shell_append (shell, item);
1058       gtk_widget_show (item);
1059     }
1060
1061   /* Separator & Block */
1062   if (features & EMPATHY_INDIVIDUAL_FEATURE_BLOCK &&
1063       (item = empathy_individiual_block_menu_item_new (individual)) != NULL) {
1064     GtkWidget *sep;
1065
1066     sep = gtk_separator_menu_item_new ();
1067     gtk_menu_shell_append (shell, sep);
1068     gtk_widget_show (sep);
1069
1070     gtk_menu_shell_append (shell, item);
1071     gtk_widget_show (item);
1072   }
1073
1074   /* Separator & Remove */
1075   if (features & EMPATHY_INDIVIDUAL_FEATURE_REMOVE &&
1076       (item = empathy_individiual_remove_menu_item_new (self)) != NULL) {
1077     GtkWidget *sep;
1078
1079     sep = gtk_separator_menu_item_new ();
1080     gtk_menu_shell_append (shell, sep);
1081     gtk_widget_show (sep);
1082
1083     gtk_menu_shell_append (shell, item);
1084     gtk_widget_show (item);
1085   }
1086 }
1087
1088 static void
1089 get_property (GObject *object,
1090     guint param_id,
1091     GValue *value,
1092     GParamSpec *pspec)
1093 {
1094   EmpathyIndividualMenuPriv *priv;
1095
1096   priv = GET_PRIV (object);
1097
1098   switch (param_id)
1099     {
1100       case PROP_ACTIVE_GROUP:
1101         g_value_set_string (value, priv->active_group);
1102         break;
1103       case PROP_INDIVIDUAL:
1104         g_value_set_object (value, priv->individual);
1105         break;
1106       case PROP_FEATURES:
1107         g_value_set_flags (value, priv->features);
1108         break;
1109       case PROP_STORE:
1110         g_value_set_object (value, priv->store);
1111         break;
1112       default:
1113         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1114         break;
1115     }
1116 }
1117
1118 static void
1119 set_property (GObject *object,
1120     guint param_id,
1121     const GValue *value,
1122     GParamSpec *pspec)
1123 {
1124   EmpathyIndividualMenuPriv *priv;
1125
1126   priv = GET_PRIV (object);
1127
1128   switch (param_id)
1129     {
1130       case PROP_ACTIVE_GROUP:
1131         g_assert (priv->active_group == NULL); /* construct only */
1132         priv->active_group = g_value_dup_string (value);
1133         break;
1134       case PROP_INDIVIDUAL:
1135         priv->individual = g_value_dup_object (value);
1136         break;
1137       case PROP_FEATURES:
1138         priv->features = g_value_get_flags (value);
1139         break;
1140       case PROP_STORE:
1141         priv->store = g_value_dup_object (value); /* read only */
1142         break;
1143       default:
1144         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1145         break;
1146     }
1147 }
1148
1149 static void
1150 dispose (GObject *object)
1151 {
1152   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1153
1154   tp_clear_object (&priv->individual);
1155   tp_clear_object (&priv->store);
1156
1157   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->dispose (object);
1158 }
1159
1160 static void
1161 finalize (GObject *object)
1162 {
1163   EmpathyIndividualMenuPriv *priv = GET_PRIV (object);
1164
1165   g_free (priv->active_group);
1166
1167   G_OBJECT_CLASS (empathy_individual_menu_parent_class)->finalize (object);
1168 }
1169
1170 static void
1171 empathy_individual_menu_class_init (EmpathyIndividualMenuClass *klass)
1172 {
1173   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1174
1175   object_class->constructed = constructed;
1176   object_class->get_property = get_property;
1177   object_class->set_property = set_property;
1178   object_class->dispose = dispose;
1179   object_class->finalize = finalize;
1180
1181   /**
1182    * gchar *:active-group:
1183    *
1184    * The group the selected roster-contact widget belongs, or NULL.
1185    */
1186   g_object_class_install_property (object_class, PROP_ACTIVE_GROUP,
1187       g_param_spec_string ("active-group",
1188           "Active group",
1189           "The group the selected roster-contact widget belongs, or NULL",
1190           NULL,
1191           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1192
1193   /**
1194    * EmpathyIndividualMenu:individual:
1195    *
1196    * The #FolksIndividual the menu is for.
1197    */
1198   g_object_class_install_property (object_class, PROP_INDIVIDUAL,
1199       g_param_spec_object ("individual",
1200           "Individual",
1201           "The #FolksIndividual the menu is for.",
1202           FOLKS_TYPE_INDIVIDUAL,
1203           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1204
1205   /**
1206    * EmpathyIndividualMenu:features:
1207    *
1208    * A set of feature flags controlling which entries are shown.
1209    */
1210   g_object_class_install_property (object_class, PROP_FEATURES,
1211       g_param_spec_flags ("features",
1212           "Features",
1213           "A set of feature flags controlling which entries are shown.",
1214           EMPATHY_TYPE_INDIVIDUAL_FEATURE_FLAGS,
1215           EMPATHY_INDIVIDUAL_FEATURE_NONE,
1216           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1217
1218   g_object_class_install_property (object_class, PROP_STORE,
1219       g_param_spec_object ("store",
1220           "Store",
1221           "The EmpathyIndividualStore to use to get contact owner",
1222           EMPATHY_TYPE_INDIVIDUAL_STORE,
1223           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
1224
1225   g_type_class_add_private (object_class, sizeof (EmpathyIndividualMenuPriv));
1226 }
1227
1228 GtkWidget *
1229 empathy_individual_menu_new (FolksIndividual *individual,
1230     const gchar *active_group,
1231     EmpathyIndividualFeatureFlags features,
1232     EmpathyIndividualStore *store)
1233 {
1234   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1235   g_return_val_if_fail (store == NULL ||
1236       EMPATHY_IS_INDIVIDUAL_STORE (store), NULL);
1237   g_return_val_if_fail (features != EMPATHY_INDIVIDUAL_FEATURE_NONE, NULL);
1238
1239   return g_object_new (EMPATHY_TYPE_INDIVIDUAL_MENU,
1240       "active-group", active_group,
1241       "individual", individual,
1242       "features", features,
1243       "store", store,
1244       NULL);
1245 }
1246
1247 /* Like menu_item_set_first_contact(), but always operates upon the given
1248  * contact. If the contact is non-NULL, it is assumed that the menu entry should
1249  * be sensitive. */
1250 static gboolean
1251 menu_item_set_contact (GtkWidget *item,
1252     EmpathyContact *contact,
1253     GCallback activate_callback,
1254     EmpathyActionType action_type)
1255 {
1256   gboolean can_do_action = FALSE;
1257
1258   if (contact != NULL)
1259     can_do_action = empathy_contact_can_do_action (contact, action_type);
1260   gtk_widget_set_sensitive (item, can_do_action);
1261
1262   if (can_do_action == TRUE)
1263     {
1264       /* We want to make sure that the EmpathyContact stays alive while the
1265        * signal is connected. */
1266       g_signal_connect_data (item, "activate", G_CALLBACK (activate_callback),
1267           g_object_ref (contact), (GClosureNotify) g_object_unref, 0);
1268     }
1269
1270   return can_do_action;
1271 }
1272
1273 /**
1274  * Set the given menu @item to call @activate_callback using the TpContact
1275  * (associated with @individual) with the highest availability who is also valid
1276  * whenever @item is activated.
1277  *
1278  * @action_type is the type of action performed by the menu entry; this is used
1279  * so that only contacts which can perform that action (e.g. are capable of
1280  * receiving video calls) are selected, as appropriate.
1281  */
1282 static GtkWidget *
1283 menu_item_set_first_contact (GtkWidget *item,
1284     FolksIndividual *individual,
1285     GCallback activate_callback,
1286     EmpathyActionType action_type)
1287 {
1288   EmpathyContact *best_contact;
1289
1290   best_contact = empathy_contact_dup_best_for_action (individual, action_type);
1291   menu_item_set_contact (item, best_contact, G_CALLBACK (activate_callback),
1292       action_type);
1293   tp_clear_object (&best_contact);
1294
1295   return item;
1296 }
1297
1298 static void
1299 empathy_individual_chat_menu_item_activated (GtkMenuItem *item,
1300   EmpathyContact *contact)
1301 {
1302   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1303
1304   empathy_chat_with_contact (contact, empathy_get_current_action_time ());
1305 }
1306
1307 static GtkWidget *
1308 empathy_individual_chat_menu_item_new (FolksIndividual *individual)
1309 {
1310   GtkWidget *item;
1311   GtkWidget *image;
1312
1313   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1314       empathy_folks_individual_contains_contact (individual), NULL);
1315
1316   item = gtk_image_menu_item_new_with_mnemonic (_("_Chat"));
1317   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_MESSAGE,
1318       GTK_ICON_SIZE_MENU);
1319   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1320   gtk_widget_show (image);
1321
1322   menu_item_set_first_contact (item, individual,
1323       G_CALLBACK (empathy_individual_chat_menu_item_activated),
1324       EMPATHY_ACTION_CHAT);
1325
1326   return item;
1327 }
1328
1329 static void
1330 empathy_individual_sms_menu_item_activated (GtkMenuItem *item,
1331   EmpathyContact *contact)
1332 {
1333   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1334
1335   empathy_sms_contact_id (
1336       empathy_contact_get_account (contact),
1337       empathy_contact_get_id (contact),
1338       empathy_get_current_action_time (),
1339       NULL, NULL);
1340 }
1341
1342 static GtkWidget *
1343 empathy_individual_sms_menu_item_new (FolksIndividual *individual)
1344 {
1345   GtkWidget *item;
1346   GtkWidget *image;
1347
1348   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) &&
1349       empathy_folks_individual_contains_contact (individual), NULL);
1350
1351   item = gtk_image_menu_item_new_with_mnemonic (_("_SMS"));
1352   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_SMS,
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   menu_item_set_first_contact (item, individual,
1358       G_CALLBACK (empathy_individual_sms_menu_item_activated),
1359       EMPATHY_ACTION_SMS);
1360
1361   return item;
1362 }
1363
1364 static void
1365 empathy_individual_audio_call_menu_item_activated (GtkMenuItem *item,
1366   EmpathyContact *contact)
1367 {
1368   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1369
1370   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1371       empathy_contact_get_account (contact),
1372       TRUE, FALSE,
1373       empathy_get_current_action_time ());
1374 }
1375
1376 GtkWidget *
1377 empathy_individual_audio_call_menu_item_new (FolksIndividual *individual)
1378 {
1379   GtkWidget *item;
1380   GtkWidget *image;
1381
1382   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1383
1384   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Audio Call"));
1385   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VOIP, GTK_ICON_SIZE_MENU);
1386   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1387   gtk_widget_show (image);
1388
1389   menu_item_set_first_contact (item, individual,
1390       G_CALLBACK (empathy_individual_audio_call_menu_item_activated),
1391       EMPATHY_ACTION_AUDIO_CALL);
1392
1393   return item;
1394 }
1395
1396 static void
1397 empathy_individual_video_call_menu_item_activated (GtkMenuItem *item,
1398   EmpathyContact *contact)
1399 {
1400   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1401
1402   empathy_call_new_with_streams (empathy_contact_get_id (contact),
1403       empathy_contact_get_account (contact),
1404       TRUE, TRUE,
1405       empathy_get_current_action_time ());
1406 }
1407
1408 GtkWidget *
1409 empathy_individual_video_call_menu_item_new (FolksIndividual *individual)
1410 {
1411   GtkWidget *item;
1412   GtkWidget *image;
1413   EmpathyCameraMonitor *monitor;
1414
1415   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1416
1417   item = gtk_image_menu_item_new_with_mnemonic (C_("menu item", "_Video Call"));
1418   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_VIDEO_CALL,
1419       GTK_ICON_SIZE_MENU);
1420   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1421   gtk_widget_show (image);
1422
1423   menu_item_set_first_contact (item, individual,
1424       G_CALLBACK (empathy_individual_video_call_menu_item_activated),
1425       EMPATHY_ACTION_VIDEO_CALL);
1426
1427   /* Only follow available cameras if the contact can do Video calls */
1428   if (gtk_widget_get_sensitive (item))
1429     {
1430       monitor = empathy_camera_monitor_dup_singleton ();
1431       g_object_set_data_full (G_OBJECT (item),
1432           "monitor", monitor, g_object_unref);
1433       g_object_bind_property (monitor, "available", item, "sensitive",
1434           G_BINDING_SYNC_CREATE);
1435     }
1436
1437   return item;
1438 }
1439
1440 static void
1441 empathy_individual_log_menu_item_activated (GtkMenuItem *item,
1442   EmpathyContact *contact)
1443 {
1444   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1445
1446   empathy_log_window_show (empathy_contact_get_account (contact),
1447       empathy_contact_get_id (contact), FALSE, NULL);
1448 }
1449
1450 static GtkWidget *
1451 empathy_individual_log_menu_item_new (FolksIndividual *individual)
1452 {
1453   GtkWidget *item;
1454   GtkWidget *image;
1455
1456   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1457
1458   item = gtk_image_menu_item_new_with_mnemonic (_("_Previous Conversations"));
1459   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_LOG, GTK_ICON_SIZE_MENU);
1460   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1461   gtk_widget_show (image);
1462
1463   menu_item_set_first_contact (item, individual,
1464       G_CALLBACK (empathy_individual_log_menu_item_activated),
1465       EMPATHY_ACTION_VIEW_LOGS);
1466
1467   return item;
1468 }
1469
1470 static void
1471 empathy_individual_file_transfer_menu_item_activated (GtkMenuItem *item,
1472     EmpathyContact *contact)
1473 {
1474   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1475
1476   empathy_send_file_with_file_chooser (contact);
1477 }
1478
1479 static GtkWidget *
1480 empathy_individual_file_transfer_menu_item_new (FolksIndividual *individual)
1481 {
1482   GtkWidget *item;
1483   GtkWidget *image;
1484
1485   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1486
1487   item = gtk_image_menu_item_new_with_mnemonic (_("Send File"));
1488   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_DOCUMENT_SEND,
1489       GTK_ICON_SIZE_MENU);
1490   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1491   gtk_widget_show (image);
1492
1493   menu_item_set_first_contact (item, individual,
1494       G_CALLBACK (empathy_individual_file_transfer_menu_item_activated),
1495       EMPATHY_ACTION_SEND_FILE);
1496
1497   return item;
1498 }
1499
1500 static void
1501 empathy_individual_share_my_desktop_menu_item_activated (GtkMenuItem *item,
1502     EmpathyContact *contact)
1503 {
1504   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1505
1506   empathy_share_my_desktop_share_with_contact (contact);
1507 }
1508
1509 static GtkWidget *
1510 empathy_individual_share_my_desktop_menu_item_new (FolksIndividual *individual)
1511 {
1512   GtkWidget *item;
1513   GtkWidget *image;
1514
1515   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1516
1517   item = gtk_image_menu_item_new_with_mnemonic (_("Share My Desktop"));
1518   image = gtk_image_new_from_icon_name (GTK_STOCK_NETWORK, GTK_ICON_SIZE_MENU);
1519   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1520   gtk_widget_show (image);
1521
1522   menu_item_set_first_contact (item, individual,
1523       G_CALLBACK (empathy_individual_share_my_desktop_menu_item_activated),
1524       EMPATHY_ACTION_SHARE_MY_DESKTOP);
1525
1526   return item;
1527 }
1528
1529 static void
1530 favourite_menu_item_toggled_cb (GtkCheckMenuItem *item,
1531   FolksIndividual *individual)
1532 {
1533   folks_favourite_details_set_is_favourite (
1534       FOLKS_FAVOURITE_DETAILS (individual),
1535       gtk_check_menu_item_get_active (item));
1536 }
1537
1538 static GtkWidget *
1539 empathy_individual_favourite_menu_item_new (FolksIndividual *individual)
1540 {
1541   GtkWidget *item;
1542
1543   item = gtk_check_menu_item_new_with_label (_("Favorite"));
1544
1545   gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
1546       folks_favourite_details_get_is_favourite (
1547           FOLKS_FAVOURITE_DETAILS (individual)));
1548
1549   g_signal_connect (item, "toggled",
1550       G_CALLBACK (favourite_menu_item_toggled_cb), individual);
1551
1552   return item;
1553 }
1554
1555 static void
1556 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1557     FolksIndividual *individual)
1558 {
1559   empathy_display_individual_info (individual);
1560 }
1561
1562 static GtkWidget *
1563 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1564 {
1565   GtkWidget *item;
1566   GtkWidget *image;
1567
1568   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1569   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1570       NULL);
1571
1572   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1573   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1574                 GTK_ICON_SIZE_MENU);
1575   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1576   gtk_widget_show (image);
1577
1578   g_signal_connect (item, "activate",
1579           G_CALLBACK (individual_info_menu_item_activate_cb),
1580           individual);
1581
1582   return item;
1583 }
1584
1585 static void
1586 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1587 {
1588   empathy_individual_edit_dialog_show (individual, NULL);
1589 }
1590
1591 static GtkWidget *
1592 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1593 {
1594   EmpathyIndividualManager *manager;
1595   GtkWidget *item;
1596   GtkWidget *image;
1597   gboolean enable = FALSE;
1598   EmpathyContact *contact;
1599
1600   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1601
1602   contact = empathy_contact_dup_from_folks_individual (individual);
1603
1604   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1605
1606   if (empathy_individual_manager_initialized ())
1607     {
1608       TpConnection *connection;
1609
1610       manager = empathy_individual_manager_dup_singleton ();
1611       connection = empathy_contact_get_connection (contact);
1612
1613       enable = (empathy_connection_can_alias_personas (connection,
1614                                                        individual) &&
1615                 empathy_connection_can_group_personas (connection, individual));
1616
1617       g_object_unref (manager);
1618     }
1619
1620   item = gtk_image_menu_item_new_with_mnemonic (
1621       C_("Edit individual (contextual menu)", "_Edit"));
1622   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1623   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1624   gtk_widget_show (image);
1625
1626   gtk_widget_set_sensitive (item, enable);
1627
1628   g_signal_connect_swapped (item, "activate",
1629       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1630
1631   g_object_unref (contact);
1632
1633   return item;
1634 }
1635
1636 typedef struct
1637 {
1638   FolksIndividual *individual;
1639   EmpathyContact *contact;
1640   EmpathyChatroom *chatroom;
1641 } RoomSubMenuData;
1642
1643 static RoomSubMenuData *
1644 room_sub_menu_data_new (FolksIndividual *individual,
1645     EmpathyContact *contact,
1646     EmpathyChatroom *chatroom)
1647 {
1648   RoomSubMenuData *data;
1649
1650   data = g_slice_new0 (RoomSubMenuData);
1651   if (individual != NULL)
1652     data->individual = g_object_ref (individual);
1653   if (contact != NULL)
1654     data->contact = g_object_ref (contact);
1655   data->chatroom = g_object_ref (chatroom);
1656
1657   return data;
1658 }
1659
1660 static void
1661 room_sub_menu_data_free (RoomSubMenuData *data)
1662 {
1663   tp_clear_object (&data->individual);
1664   tp_clear_object (&data->contact);
1665   g_object_unref (data->chatroom);
1666   g_slice_free (RoomSubMenuData, data);
1667 }
1668
1669 static void
1670 room_sub_menu_activate_cb (GtkWidget *item,
1671          RoomSubMenuData *data)
1672 {
1673   EmpathyTpChat *chat;
1674   EmpathyChatroomManager *mgr;
1675   EmpathyContact *contact = NULL;
1676
1677   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1678   if (chat == NULL)
1679     {
1680       /* channel was invalidated. Ignoring */
1681       return;
1682     }
1683
1684   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1685
1686   if (data->contact != NULL)
1687     contact = g_object_ref (data->contact);
1688   else
1689     {
1690       GeeSet *personas;
1691       GeeIterator *iter;
1692
1693       /* find the first of this Individual's contacts who can join this room */
1694       personas = folks_individual_get_personas (data->individual);
1695
1696       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1697       while (gee_iterator_next (iter) && (contact == NULL))
1698         {
1699           TpfPersona *persona = gee_iterator_get (iter);
1700           TpContact *tp_contact;
1701           GList *rooms;
1702
1703           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1704             {
1705               tp_contact = tpf_persona_get_contact (persona);
1706               if (tp_contact != NULL)
1707                 {
1708                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1709
1710                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1711                       empathy_contact_get_account (contact));
1712
1713                   if (g_list_find (rooms, data->chatroom) == NULL)
1714                     g_clear_object (&contact);
1715
1716                   /* if contact != NULL here, we've found our match */
1717
1718                   g_list_free (rooms);
1719                 }
1720             }
1721           g_clear_object (&persona);
1722         }
1723       g_clear_object (&iter);
1724     }
1725
1726   g_object_unref (mgr);
1727
1728   if (contact == NULL)
1729     {
1730       /* contact disappeared. Ignoring */
1731       goto out;
1732     }
1733
1734   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1735
1736   /* send invitation */
1737   empathy_tp_chat_add (chat, contact, _("Inviting you to this room"));
1738
1739 out:
1740   g_object_unref (contact);
1741 }
1742
1743 static GtkWidget *
1744 create_room_sub_menu (FolksIndividual *individual,
1745                       EmpathyContact *contact,
1746                       EmpathyChatroom *chatroom)
1747 {
1748   GtkWidget *item;
1749   RoomSubMenuData *data;
1750
1751   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1752   data = room_sub_menu_data_new (individual, contact, chatroom);
1753   g_signal_connect_data (item, "activate",
1754       G_CALLBACK (room_sub_menu_activate_cb), data,
1755       (GClosureNotify) room_sub_menu_data_free, 0);
1756
1757   return item;
1758 }
1759
1760 static GtkWidget *
1761 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1762     EmpathyContact *contact)
1763 {
1764   GtkWidget *item;
1765   GtkWidget *image;
1766   GtkWidget *room_item;
1767   EmpathyChatroomManager *mgr;
1768   GList *rooms = NULL;
1769   GList *names = NULL;
1770   GList *l;
1771   GtkWidget *submenu = NULL;
1772   /* map of chat room names to their objects; just a utility to remove
1773    * duplicates and to make construction of the alphabetized list easier */
1774   GHashTable *name_room_map;
1775
1776   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1777       EMPATHY_IS_CONTACT (contact),
1778       NULL);
1779
1780   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1781       g_object_unref);
1782
1783   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1784   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1785       GTK_ICON_SIZE_MENU);
1786   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1787
1788   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1789
1790   if (contact != NULL)
1791     {
1792       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1793           empathy_contact_get_account (contact));
1794     }
1795   else
1796     {
1797       GeeSet *personas;
1798       GeeIterator *iter;
1799
1800       /* find the first of this Individual's contacts who can join this room */
1801       personas = folks_individual_get_personas (individual);
1802       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1803       while (gee_iterator_next (iter))
1804         {
1805           TpfPersona *persona = gee_iterator_get (iter);
1806           GList *rooms_cur;
1807           TpContact *tp_contact;
1808           EmpathyContact *contact_cur;
1809
1810           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1811             {
1812               tp_contact = tpf_persona_get_contact (persona);
1813               if (tp_contact != NULL)
1814                 {
1815                   contact_cur = empathy_contact_dup_from_tp_contact (
1816                       tp_contact);
1817
1818                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1819                       empathy_contact_get_account (contact_cur));
1820                   rooms = g_list_concat (rooms, rooms_cur);
1821
1822                   g_object_unref (contact_cur);
1823                 }
1824             }
1825           g_clear_object (&persona);
1826         }
1827       g_clear_object (&iter);
1828     }
1829
1830   /* alphabetize the rooms */
1831   for (l = rooms; l != NULL; l = g_list_next (l))
1832     {
1833       EmpathyChatroom *chatroom = l->data;
1834       gboolean existed;
1835
1836       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1837         {
1838           const gchar *name;
1839
1840           name = empathy_chatroom_get_name (chatroom);
1841           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1842           g_hash_table_insert (name_room_map, (gpointer) name,
1843               g_object_ref (chatroom));
1844
1845           /* this will take care of duplicates in rooms */
1846           if (!existed)
1847             {
1848               names = g_list_insert_sorted (names, (gpointer) name,
1849                   (GCompareFunc) g_strcmp0);
1850             }
1851         }
1852     }
1853
1854   for (l = names; l != NULL; l = g_list_next (l))
1855     {
1856       const gchar *name = l->data;
1857       EmpathyChatroom *chatroom;
1858
1859       if (G_UNLIKELY (submenu == NULL))
1860         submenu = gtk_menu_new ();
1861
1862       chatroom = g_hash_table_lookup (name_room_map, name);
1863       room_item = create_room_sub_menu (individual, contact, chatroom);
1864       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1865       gtk_widget_show (room_item);
1866     }
1867
1868   if (submenu)
1869     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1870   else
1871     gtk_widget_set_sensitive (item, FALSE);
1872
1873   gtk_widget_show (image);
1874
1875   g_hash_table_unref (name_room_map);
1876   g_object_unref (mgr);
1877   g_list_free (names);
1878   g_list_free (rooms);
1879
1880   return item;
1881 }
1882
1883 static void
1884 add_menu_item_activated (GtkMenuItem *item,
1885     TpContact *tp_contact)
1886 {
1887   GtkWidget *toplevel;
1888   FolksIndividual *individual;
1889
1890   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1891   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1892     toplevel = NULL;
1893
1894   individual = empathy_ensure_individual_from_tp_contact (tp_contact);
1895
1896   empathy_new_individual_dialog_show_with_individual (GTK_WINDOW (toplevel),
1897       individual);
1898
1899   g_object_unref (individual);
1900 }
1901
1902 static GtkWidget *
1903 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1904     FolksIndividual *individual)
1905 {
1906   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1907   GtkWidget *item, *image;
1908   GeeSet *personas;
1909   GeeIterator *iter;
1910   TpContact *to_add = NULL;
1911
1912   /* find the first of this Individual's personas which are not in our contact
1913    * list. */
1914   personas = folks_individual_get_personas (individual);
1915   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1916   while (gee_iterator_next (iter))
1917     {
1918       TpfPersona *persona = gee_iterator_get (iter);
1919       TpContact *contact;
1920       TpConnection *conn;
1921
1922       if (!TPF_IS_PERSONA (persona))
1923         goto next;
1924
1925       contact = tpf_persona_get_contact (persona);
1926       if (contact == NULL)
1927         goto next;
1928
1929       /* be sure to use a not channel specific contact.
1930        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
1931       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
1932         {
1933           TpChannel *channel;
1934           TpChannelGroupFlags flags;
1935
1936           channel = empathy_individual_store_channel_get_channel (
1937               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
1938
1939           flags = tp_channel_group_get_flags (channel);
1940           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
1941             {
1942               /* Channel uses channel specific handles (thanks XMPP...) */
1943               contact = tp_channel_group_get_contact_owner (channel, contact);
1944
1945               /* If we don't know the owner, we can't add the contact */
1946               if (contact == NULL)
1947                 goto next;
1948             }
1949         }
1950
1951       conn = tp_contact_get_connection (contact);
1952       if (conn == NULL)
1953         goto next;
1954
1955       /* No point to try adding a contact if the CM doesn't support it */
1956       if (!tp_connection_get_can_change_contact_list (conn))
1957         goto next;
1958
1959       /* Can't add ourself */
1960       if (tp_connection_get_self_contact (conn) == contact)
1961         goto next;
1962
1963       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
1964         goto next;
1965
1966       g_object_unref (persona);
1967       to_add = contact;
1968       break;
1969
1970 next:
1971       g_object_unref (persona);
1972     }
1973
1974   g_object_unref (iter);
1975
1976   if (to_add == NULL)
1977     return NULL;
1978
1979   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
1980   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1981   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1982
1983   g_signal_connect_data (item, "activate",
1984       G_CALLBACK (add_menu_item_activated),
1985       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
1986
1987   return item;
1988 }