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