]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-individual-menu.c
fix individual_info_menu_item_activate_cb signature
[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   /* Start gnome-contacts */
1315   display = gdk_display_get_default ();
1316   context = gdk_display_get_app_launch_context (display);
1317
1318   desktop_info = g_desktop_app_info_new ("gnome-contacts.desktop");
1319   if (desktop_info == NULL)
1320     {
1321       if (try_installing)
1322         {
1323           const gchar *packages[] = { "gnome-contacts", NULL };
1324
1325           DEBUG ("gnome-contacts not installed; try to install it");
1326
1327           empathy_pkg_kit_install_packages_async (0, packages, NULL,
1328               NULL, install_gnome_contacts_cb, g_object_ref (individual));
1329         }
1330       else
1331         {
1332           show_gnome_contacts_error_dialog ();
1333         }
1334
1335       return;
1336     }
1337
1338   /* glib doesn't have API to start a desktop file with args... (#637875) */
1339   cmd = g_strdup_printf ("%s -i %s", g_app_info_get_commandline (
1340         (GAppInfo *) desktop_info), folks_individual_get_id (individual));
1341
1342   app_info = g_app_info_create_from_commandline (cmd, NULL, 0, &error);
1343   if (app_info == NULL)
1344     {
1345       DEBUG ("Failed to create app_info: %s", error->message);
1346       g_error_free (error);
1347       return;
1348     }
1349
1350   if (!g_app_info_launch (app_info, NULL, (GAppLaunchContext *) context,
1351         &error))
1352     {
1353       g_critical ("Failed to start gnome-contacts: %s", error->message);
1354       g_error_free (error);
1355     }
1356
1357   g_object_unref (desktop_info);
1358   g_object_unref (app_info);
1359 }
1360
1361 static void
1362 individual_info_menu_item_activate_cb (GtkMenuItem *item,
1363     FolksIndividual *individual)
1364 {
1365   start_gnome_contacts (individual, TRUE);
1366 }
1367
1368 static GtkWidget *
1369 empathy_individual_info_menu_item_new (FolksIndividual *individual)
1370 {
1371   GtkWidget *item;
1372   GtkWidget *image;
1373
1374   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1375   g_return_val_if_fail (empathy_folks_individual_contains_contact (individual),
1376       NULL);
1377
1378   item = gtk_image_menu_item_new_with_mnemonic (_("Infor_mation"));
1379   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_CONTACT_INFORMATION,
1380                 GTK_ICON_SIZE_MENU);
1381   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1382   gtk_widget_show (image);
1383
1384   g_signal_connect (item, "activate",
1385           G_CALLBACK (individual_info_menu_item_activate_cb),
1386           individual);
1387
1388   return item;
1389 }
1390
1391 static void
1392 individual_edit_menu_item_activate_cb (FolksIndividual *individual)
1393 {
1394   empathy_individual_edit_dialog_show (individual, NULL);
1395 }
1396
1397 static GtkWidget *
1398 empathy_individual_edit_menu_item_new (FolksIndividual *individual)
1399 {
1400   EmpathyIndividualManager *manager;
1401   GtkWidget *item;
1402   GtkWidget *image;
1403   gboolean enable = FALSE;
1404   EmpathyContact *contact;
1405
1406   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual), NULL);
1407
1408   contact = empathy_contact_dup_from_folks_individual (individual);
1409
1410   g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
1411
1412   if (empathy_individual_manager_initialized ())
1413     {
1414       TpConnection *connection;
1415
1416       manager = empathy_individual_manager_dup_singleton ();
1417       connection = empathy_contact_get_connection (contact);
1418
1419       enable = (empathy_connection_can_alias_personas (connection,
1420                                                        individual) &&
1421                 empathy_connection_can_group_personas (connection, individual));
1422
1423       g_object_unref (manager);
1424     }
1425
1426   item = gtk_image_menu_item_new_with_mnemonic (
1427       C_("Edit individual (contextual menu)", "_Edit"));
1428   image = gtk_image_new_from_icon_name (GTK_STOCK_EDIT, GTK_ICON_SIZE_MENU);
1429   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1430   gtk_widget_show (image);
1431
1432   gtk_widget_set_sensitive (item, enable);
1433
1434   g_signal_connect_swapped (item, "activate",
1435       G_CALLBACK (individual_edit_menu_item_activate_cb), individual);
1436
1437   g_object_unref (contact);
1438
1439   return item;
1440 }
1441
1442 typedef struct
1443 {
1444   FolksIndividual *individual;
1445   EmpathyContact *contact;
1446   EmpathyChatroom *chatroom;
1447 } RoomSubMenuData;
1448
1449 static RoomSubMenuData *
1450 room_sub_menu_data_new (FolksIndividual *individual,
1451     EmpathyContact *contact,
1452     EmpathyChatroom *chatroom)
1453 {
1454   RoomSubMenuData *data;
1455
1456   data = g_slice_new0 (RoomSubMenuData);
1457   if (individual != NULL)
1458     data->individual = g_object_ref (individual);
1459   if (contact != NULL)
1460     data->contact = g_object_ref (contact);
1461   data->chatroom = g_object_ref (chatroom);
1462
1463   return data;
1464 }
1465
1466 static void
1467 room_sub_menu_data_free (RoomSubMenuData *data)
1468 {
1469   tp_clear_object (&data->individual);
1470   tp_clear_object (&data->contact);
1471   g_object_unref (data->chatroom);
1472   g_slice_free (RoomSubMenuData, data);
1473 }
1474
1475 static void
1476 room_sub_menu_activate_cb (GtkWidget *item,
1477          RoomSubMenuData *data)
1478 {
1479   EmpathyTpChat *chat;
1480   EmpathyChatroomManager *mgr;
1481   EmpathyContact *contact = NULL;
1482
1483   chat = empathy_chatroom_get_tp_chat (data->chatroom);
1484   if (chat == NULL)
1485     {
1486       /* channel was invalidated. Ignoring */
1487       return;
1488     }
1489
1490   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1491
1492   if (data->contact != NULL)
1493     contact = g_object_ref (data->contact);
1494   else
1495     {
1496       GeeSet *personas;
1497       GeeIterator *iter;
1498
1499       /* find the first of this Individual's contacts who can join this room */
1500       personas = folks_individual_get_personas (data->individual);
1501
1502       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1503       while (gee_iterator_next (iter) && (contact == NULL))
1504         {
1505           TpfPersona *persona = gee_iterator_get (iter);
1506           TpContact *tp_contact;
1507           GList *rooms;
1508
1509           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1510             {
1511               tp_contact = tpf_persona_get_contact (persona);
1512               if (tp_contact != NULL)
1513                 {
1514                   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1515
1516                   rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1517                       empathy_contact_get_account (contact));
1518
1519                   if (g_list_find (rooms, data->chatroom) == NULL)
1520                     g_clear_object (&contact);
1521
1522                   /* if contact != NULL here, we've found our match */
1523
1524                   g_list_free (rooms);
1525                 }
1526             }
1527           g_clear_object (&persona);
1528         }
1529       g_clear_object (&iter);
1530     }
1531
1532   g_object_unref (mgr);
1533
1534   if (contact == NULL)
1535     {
1536       /* contact disappeared. Ignoring */
1537       goto out;
1538     }
1539
1540   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
1541
1542   /* send invitation */
1543   empathy_contact_list_add (EMPATHY_CONTACT_LIST (chat),
1544       contact, _("Inviting you to this room"));
1545
1546 out:
1547   g_object_unref (contact);
1548 }
1549
1550 static GtkWidget *
1551 create_room_sub_menu (FolksIndividual *individual,
1552                       EmpathyContact *contact,
1553                       EmpathyChatroom *chatroom)
1554 {
1555   GtkWidget *item;
1556   RoomSubMenuData *data;
1557
1558   item = gtk_menu_item_new_with_label (empathy_chatroom_get_name (chatroom));
1559   data = room_sub_menu_data_new (individual, contact, chatroom);
1560   g_signal_connect_data (item, "activate",
1561       G_CALLBACK (room_sub_menu_activate_cb), data,
1562       (GClosureNotify) room_sub_menu_data_free, 0);
1563
1564   return item;
1565 }
1566
1567 static GtkWidget *
1568 empathy_individual_invite_menu_item_new (FolksIndividual *individual,
1569     EmpathyContact *contact)
1570 {
1571   GtkWidget *item;
1572   GtkWidget *image;
1573   GtkWidget *room_item;
1574   EmpathyChatroomManager *mgr;
1575   GList *rooms = NULL;
1576   GList *names = NULL;
1577   GList *l;
1578   GtkWidget *submenu = NULL;
1579   /* map of chat room names to their objects; just a utility to remove
1580    * duplicates and to make construction of the alphabetized list easier */
1581   GHashTable *name_room_map;
1582
1583   g_return_val_if_fail (FOLKS_IS_INDIVIDUAL (individual) ||
1584       EMPATHY_IS_CONTACT (contact),
1585       NULL);
1586
1587   name_room_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
1588       g_object_unref);
1589
1590   item = gtk_image_menu_item_new_with_mnemonic (_("_Invite to Chat Room"));
1591   image = gtk_image_new_from_icon_name (EMPATHY_IMAGE_GROUP_MESSAGE,
1592       GTK_ICON_SIZE_MENU);
1593   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1594
1595   mgr = empathy_chatroom_manager_dup_singleton (NULL);
1596
1597   if (contact != NULL)
1598     {
1599       rooms = empathy_chatroom_manager_get_chatrooms (mgr,
1600           empathy_contact_get_account (contact));
1601     }
1602   else
1603     {
1604       GeeSet *personas;
1605       GeeIterator *iter;
1606
1607       /* find the first of this Individual's contacts who can join this room */
1608       personas = folks_individual_get_personas (individual);
1609       iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1610       while (gee_iterator_next (iter))
1611         {
1612           TpfPersona *persona = gee_iterator_get (iter);
1613           GList *rooms_cur;
1614           TpContact *tp_contact;
1615           EmpathyContact *contact_cur;
1616
1617           if (empathy_folks_persona_is_interesting (FOLKS_PERSONA (persona)))
1618             {
1619               tp_contact = tpf_persona_get_contact (persona);
1620               if (tp_contact != NULL)
1621                 {
1622                   contact_cur = empathy_contact_dup_from_tp_contact (
1623                       tp_contact);
1624
1625                   rooms_cur = empathy_chatroom_manager_get_chatrooms (mgr,
1626                       empathy_contact_get_account (contact_cur));
1627                   rooms = g_list_concat (rooms, rooms_cur);
1628
1629                   g_object_unref (contact_cur);
1630                 }
1631             }
1632           g_clear_object (&persona);
1633         }
1634       g_clear_object (&iter);
1635     }
1636
1637   /* alphabetize the rooms */
1638   for (l = rooms; l != NULL; l = g_list_next (l))
1639     {
1640       EmpathyChatroom *chatroom = l->data;
1641       gboolean existed;
1642
1643       if (empathy_chatroom_get_tp_chat (chatroom) != NULL)
1644         {
1645           const gchar *name;
1646
1647           name = empathy_chatroom_get_name (chatroom);
1648           existed = (g_hash_table_lookup (name_room_map, name) != NULL);
1649           g_hash_table_insert (name_room_map, (gpointer) name,
1650               g_object_ref (chatroom));
1651
1652           /* this will take care of duplicates in rooms */
1653           if (!existed)
1654             {
1655               names = g_list_insert_sorted (names, (gpointer) name,
1656                   (GCompareFunc) g_strcmp0);
1657             }
1658         }
1659     }
1660
1661   for (l = names; l != NULL; l = g_list_next (l))
1662     {
1663       const gchar *name = l->data;
1664       EmpathyChatroom *chatroom;
1665
1666       if (G_UNLIKELY (submenu == NULL))
1667         submenu = gtk_menu_new ();
1668
1669       chatroom = g_hash_table_lookup (name_room_map, name);
1670       room_item = create_room_sub_menu (individual, contact, chatroom);
1671       gtk_menu_shell_append ((GtkMenuShell *) submenu, room_item);
1672       gtk_widget_show (room_item);
1673     }
1674
1675   if (submenu)
1676     gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
1677   else
1678     gtk_widget_set_sensitive (item, FALSE);
1679
1680   gtk_widget_show (image);
1681
1682   g_hash_table_unref (name_room_map);
1683   g_object_unref (mgr);
1684   g_list_free (names);
1685   g_list_free (rooms);
1686
1687   return item;
1688 }
1689
1690 static void
1691 add_menu_item_activated (GtkMenuItem *item,
1692     TpContact *tp_contact)
1693 {
1694   GtkWidget *toplevel;
1695   EmpathyContact *contact;
1696
1697   toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item));
1698   if (!gtk_widget_is_toplevel (toplevel) || !GTK_IS_WINDOW (toplevel))
1699     toplevel = NULL;
1700
1701   contact = empathy_contact_dup_from_tp_contact (tp_contact);
1702
1703   empathy_new_contact_dialog_show_with_contact (GTK_WINDOW (toplevel),
1704       contact);
1705
1706   g_object_unref (contact);
1707 }
1708
1709 static GtkWidget *
1710 empathy_individual_add_menu_item_new (EmpathyIndividualMenu *self,
1711     FolksIndividual *individual)
1712 {
1713   EmpathyIndividualMenuPriv *priv = GET_PRIV (self);
1714   GtkWidget *item, *image;
1715   GeeSet *personas;
1716   GeeIterator *iter;
1717   TpContact *to_add = NULL;
1718
1719   /* find the first of this Individual's personas which are not in our contact
1720    * list. */
1721   personas = folks_individual_get_personas (individual);
1722   iter = gee_iterable_iterator (GEE_ITERABLE (personas));
1723   while (gee_iterator_next (iter))
1724     {
1725       TpfPersona *persona = gee_iterator_get (iter);
1726       TpContact *contact;
1727       TpConnection *conn;
1728
1729       if (!TPF_IS_PERSONA (persona))
1730         goto next;
1731
1732       contact = tpf_persona_get_contact (persona);
1733       if (contact == NULL)
1734         goto next;
1735
1736       /* be sure to use a not channel specific contact.
1737        * TODO: Ideally tp-glib should do this for us (fdo #42702)*/
1738       if (EMPATHY_IS_INDIVIDUAL_STORE_CHANNEL (priv->store))
1739         {
1740           TpChannel *channel;
1741           TpChannelGroupFlags flags;
1742
1743           channel = empathy_individual_store_channel_get_channel (
1744               EMPATHY_INDIVIDUAL_STORE_CHANNEL (priv->store));
1745
1746           flags = tp_channel_group_get_flags (channel);
1747           if ((flags & TP_CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES) != 0)
1748             {
1749               /* Channel uses channel specific handles (thanks XMPP...) */
1750               contact = tp_channel_group_get_contact_owner (channel, contact);
1751
1752               /* If we don't know the owner, we can't add the contact */
1753               if (contact == NULL)
1754                 goto next;
1755             }
1756         }
1757
1758       conn = tp_contact_get_connection (contact);
1759       if (conn == NULL)
1760         goto next;
1761
1762       /* No point to try adding a contact if the CM doesn't support it */
1763       if (!tp_connection_get_can_change_contact_list (conn))
1764         goto next;
1765
1766       /* Can't add ourself */
1767       if (tp_connection_get_self_contact (conn) == contact)
1768         goto next;
1769
1770       if (tp_contact_get_subscribe_state (contact) == TP_SUBSCRIPTION_STATE_YES)
1771         goto next;
1772
1773       g_object_unref (persona);
1774       to_add = contact;
1775       break;
1776
1777 next:
1778       g_object_unref (persona);
1779     }
1780
1781   g_object_unref (iter);
1782
1783   if (to_add == NULL)
1784     return NULL;
1785
1786   item = gtk_image_menu_item_new_with_mnemonic (_("_Add Contact…"));
1787   image = gtk_image_new_from_icon_name (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU);
1788   gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1789
1790   g_signal_connect_data (item, "activate",
1791       G_CALLBACK (add_menu_item_activated),
1792       g_object_ref (to_add), (GClosureNotify) g_object_unref, 0);
1793
1794   return item;
1795 }