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