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