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